@webex/plugin-meetings 1.153.1 → 1.154.0
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 +20 -13
- package/dist/constants.js.map +1 -1
- package/dist/meeting/index.js +54 -41
- 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/meetings/index.js +8 -1
- package/dist/meetings/index.js.map +1 -1
- package/dist/metrics/config.js +3 -1
- package/dist/metrics/config.js.map +1 -1
- package/dist/metrics/index.js +23 -9
- package/dist/metrics/index.js.map +1 -1
- package/dist/peer-connection-manager/index.js +63 -56
- package/dist/peer-connection-manager/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +1 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/handler.js +4 -4
- package/dist/roap/handler.js.map +1 -1
- package/package.json +5 -5
- package/src/common/errors/webex-errors.js +21 -2
- package/src/constants.js +16 -11
- package/src/meeting/index.js +85 -68
- package/src/meeting/request.js +10 -5
- package/src/meeting/state.js +8 -17
- package/src/meeting/util.js +2 -1
- package/src/meetings/index.js +14 -3
- package/src/metrics/config.js +1 -0
- package/src/metrics/index.js +23 -9
- package/src/peer-connection-manager/index.js +61 -60
- package/src/reconnection-manager/index.js +3 -3
- package/src/roap/handler.js +5 -5
- package/test/unit/spec/meeting/index.js +39 -7
- 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/metrics/index.js +6 -6
- package/test/unit/spec/peerconnection-manager/index.js +69 -0
package/src/metrics/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import BrowserDetection from '../common/browser-detection';
|
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
16
|
error, eventType, errorCodes as ERROR_CODE, OS_NAME, UNKNOWN, CLIENT_NAME,
|
|
17
|
-
mediaType
|
|
17
|
+
mediaType, PLATFORM
|
|
18
18
|
} from './config';
|
|
19
19
|
|
|
20
20
|
const OSMap = {
|
|
@@ -489,11 +489,11 @@ class Metrics {
|
|
|
489
489
|
}
|
|
490
490
|
|
|
491
491
|
/**
|
|
492
|
-
* Uploads given metric to the Metrics service as an
|
|
492
|
+
* Uploads given metric to the Metrics service as an Behavioral metric.
|
|
493
493
|
* Metadata about the environment such as browser, OS, SDK and their versions
|
|
494
494
|
* are automatically added when the metric is sent.
|
|
495
495
|
*
|
|
496
|
-
* The Metrics service will send an
|
|
496
|
+
* The Metrics service will send an Behavioral metric to InfluxDB for
|
|
497
497
|
* aggregation.
|
|
498
498
|
* See https://confluence-eng-gpk2.cisco.com/conf/display/WBXT/Getting+started+with+Metrics+Service.
|
|
499
499
|
*
|
|
@@ -503,12 +503,24 @@ class Metrics {
|
|
|
503
503
|
*
|
|
504
504
|
* @returns {void}
|
|
505
505
|
*/
|
|
506
|
-
|
|
506
|
+
sendBehavioralMetric(metricName, metricFields = {}, metricTags = {}) {
|
|
507
507
|
const fields = {
|
|
508
508
|
...metricFields,
|
|
509
509
|
browser_version: getBrowserVersion(),
|
|
510
510
|
os_version: getOSVersion(),
|
|
511
|
-
sdk_version: this.webex.version
|
|
511
|
+
sdk_version: this.webex.version,
|
|
512
|
+
platform: PLATFORM
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const context = {
|
|
516
|
+
app: {
|
|
517
|
+
version: this.webex.version
|
|
518
|
+
},
|
|
519
|
+
locale: 'en-US',
|
|
520
|
+
os: {
|
|
521
|
+
name: getOSName(),
|
|
522
|
+
version: getOSVersion()
|
|
523
|
+
}
|
|
512
524
|
};
|
|
513
525
|
|
|
514
526
|
const tags = {
|
|
@@ -517,17 +529,19 @@ class Metrics {
|
|
|
517
529
|
org_id: this.webex.credentials.getOrgId(),
|
|
518
530
|
os: getOSName(),
|
|
519
531
|
domain: window.location.hostname,
|
|
520
|
-
client_id: this.webex.credentials.config.client_id
|
|
532
|
+
client_id: this.webex.credentials.config.client_id,
|
|
533
|
+
user_id: this.webex.internal.device.userId
|
|
521
534
|
};
|
|
522
535
|
|
|
523
536
|
if (!metricName) {
|
|
524
|
-
throw Error('Missing
|
|
537
|
+
throw Error('Missing behavioral metric name. Please provide one');
|
|
525
538
|
}
|
|
526
539
|
|
|
527
540
|
this.webex.internal.metrics.submitClientMetrics(metricName, {
|
|
528
|
-
type: ['operational'],
|
|
541
|
+
type: ['behavioral', 'operational'],
|
|
529
542
|
fields,
|
|
530
|
-
tags
|
|
543
|
+
tags,
|
|
544
|
+
context
|
|
531
545
|
});
|
|
532
546
|
}
|
|
533
547
|
}
|
|
@@ -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,13 +21,12 @@ import {
|
|
|
22
21
|
OFFER,
|
|
23
22
|
QUALITY_LEVELS,
|
|
24
23
|
MAX_FRAMESIZES,
|
|
25
|
-
|
|
26
|
-
IPV4_REGEX
|
|
24
|
+
BEHAVIORAL_METRICS
|
|
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
|
|
|
34
32
|
import PeerConnectionUtils from './util';
|
|
@@ -122,18 +120,10 @@ const isSdpInvalid = (sdp) => {
|
|
|
122
120
|
const parsedSdp = sdpTransform.parse(sdp);
|
|
123
121
|
|
|
124
122
|
for (const mediaLine of parsedSdp.media) {
|
|
125
|
-
if (mediaLine.candidates
|
|
123
|
+
if (!mediaLine.candidates || mediaLine.candidates?.length === 0) {
|
|
126
124
|
LoggerProxy.logger.error('PeerConnectionManager:index#isSdpInvalid --> iceCandidate: Ice candadate never completed');
|
|
127
125
|
|
|
128
|
-
return 'iceCandidate: Ice
|
|
129
|
-
}
|
|
130
|
-
// Sometimes the candidates might be there but only IPV6 we need to makes sure we have IPV4
|
|
131
|
-
const hostCandidate = mediaLine.candidates.filter((candidate) => !!(candidate.type === HOST && candidate.ip.match(IPV4_REGEX)));
|
|
132
|
-
|
|
133
|
-
if (hostCandidate.length === 0) {
|
|
134
|
-
LoggerProxy.logger.error('PeerConnectionManager:index#isSdpInvalid --> iceCandidate: no IPV4 candidate present');
|
|
135
|
-
|
|
136
|
-
return 'iceCandidate: no IPV4 candidate present';
|
|
126
|
+
return 'iceCandidate: Ice gathering never completed';
|
|
137
127
|
}
|
|
138
128
|
|
|
139
129
|
if (SDP.BAD_MEDIA_PORTS.includes(mediaLine.port)) {
|
|
@@ -193,45 +183,52 @@ pc.setContentSlides = (screenPc) => {
|
|
|
193
183
|
*/
|
|
194
184
|
pc.iceCandidate = (peerConnection, {remoteQualityLevel}) =>
|
|
195
185
|
new Promise((resolve, reject) => {
|
|
196
|
-
|
|
197
|
-
const
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
const doneGatheringIceCandidate = () => {
|
|
188
|
+
const miliseconds = parseInt(Math.abs(Date.now() - now), 4);
|
|
189
|
+
|
|
198
190
|
peerConnection.sdp = limitBandwidth(peerConnection.localDescription.sdp);
|
|
199
191
|
peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
|
|
200
192
|
peerConnection.sdp = PeerConnectionUtils.convertCLineToIpv4(peerConnection.sdp);
|
|
201
193
|
|
|
202
|
-
|
|
203
|
-
setTimeout(() => {
|
|
204
|
-
// peerconnection does gather ice candidate IP but in some cases due to firewall
|
|
205
|
-
// or proxy the ice candidate does not get gathered so we need to wait and then retry
|
|
206
|
-
// if still not valid then throw an error saying missing ice candidate
|
|
207
|
-
// if ice candidate still not present after retry
|
|
208
|
-
const invalidSdpPresent = isSdpInvalid(peerConnection.sdp);
|
|
194
|
+
const invalidSdpPresent = isSdpInvalid(peerConnection.sdp);
|
|
209
195
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
else {
|
|
214
|
-
LoggerProxy.logger.error('PeerConnectionManager:index#iceCandidate --> SDP not valid after waiting.');
|
|
215
|
-
reject(new InvalidSdpError(invalidSdpPresent));
|
|
216
|
-
}
|
|
217
|
-
}, RETRY_TIMEOUT);
|
|
196
|
+
if (invalidSdpPresent) {
|
|
197
|
+
LoggerProxy.logger.error('PeerConnectionManager:index#iceCandidate --> SDP not valid after waiting.');
|
|
198
|
+
reject(new InvalidSdpError(invalidSdpPresent));
|
|
218
199
|
}
|
|
219
|
-
|
|
220
|
-
|
|
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);
|
|
221
214
|
}
|
|
222
|
-
|
|
215
|
+
if (peerConnection.iceGatheringState === GATHERING) {
|
|
216
|
+
LoggerProxy.logger.log('PeerConnectionManager:index#onIceGatheringStateChange --> Ice state changed to gathering');
|
|
217
|
+
}
|
|
218
|
+
};
|
|
223
219
|
|
|
224
220
|
peerConnection.onicecandidate = (evt) => {
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
|
|
228
|
-
peerConnection.sdp = PeerConnectionUtils.convertCLineToIpv4(peerConnection.sdp);
|
|
229
|
-
|
|
230
|
-
if (evt.candidate === null && !isSdpInvalid(peerConnection.sdp)) {
|
|
231
|
-
clearTimeout(timeout);
|
|
232
|
-
resolve(peerConnection);
|
|
233
|
-
}
|
|
221
|
+
if (evt.candidate === null) {
|
|
222
|
+
doneGatheringIceCandidate(peerConnection);
|
|
234
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());
|
|
235
232
|
};
|
|
236
233
|
});
|
|
237
234
|
|
|
@@ -345,7 +342,7 @@ pc.setRemoteSessionDetails = (
|
|
|
345
342
|
LoggerProxy.logger.error(`Peer-connection-manager:index#setRemoteDescription --> ${error} missing remotesdp`);
|
|
346
343
|
|
|
347
344
|
|
|
348
|
-
const metricName =
|
|
345
|
+
const metricName = BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE;
|
|
349
346
|
const data = {
|
|
350
347
|
correlation_id: meetingId,
|
|
351
348
|
reason: error.message,
|
|
@@ -355,7 +352,7 @@ pc.setRemoteSessionDetails = (
|
|
|
355
352
|
type: error.name
|
|
356
353
|
};
|
|
357
354
|
|
|
358
|
-
Metrics.
|
|
355
|
+
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
359
356
|
|
|
360
357
|
return Metrics.postEvent({
|
|
361
358
|
event: eventType.REMOTE_SDP_RECEIVED,
|
|
@@ -406,9 +403,6 @@ pc.createOffer = (peerConnection, {
|
|
|
406
403
|
})
|
|
407
404
|
.then(() => pc.iceCandidate(peerConnection, {remoteQualityLevel}))
|
|
408
405
|
.then(() => {
|
|
409
|
-
peerConnection.sdp = limitBandwidth(peerConnection.localDescription.sdp);
|
|
410
|
-
peerConnection.sdp = setMaxFs(peerConnection.sdp, remoteQualityLevel);
|
|
411
|
-
peerConnection.sdp = PeerConnectionUtils.convertCLineToIpv4(peerConnection.sdp);
|
|
412
406
|
if (!checkH264Support(peerConnection.sdp)) {
|
|
413
407
|
throw new MediaError('openH264 is downloading please Wait. Upload logs if not working on second try');
|
|
414
408
|
}
|
|
@@ -429,8 +423,8 @@ pc.createOffer = (peerConnection, {
|
|
|
429
423
|
.catch((error) => {
|
|
430
424
|
LoggerProxy.logger.error(`Peer-connection-manager:index#createOffer --> ${error}`);
|
|
431
425
|
if (error instanceof InvalidSdpError) {
|
|
432
|
-
Metrics.
|
|
433
|
-
|
|
426
|
+
Metrics.sendBehavioralMetric(
|
|
427
|
+
BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
|
|
434
428
|
{
|
|
435
429
|
correlation_id: meetingId,
|
|
436
430
|
code: error.code,
|
|
@@ -439,7 +433,7 @@ pc.createOffer = (peerConnection, {
|
|
|
439
433
|
);
|
|
440
434
|
}
|
|
441
435
|
else {
|
|
442
|
-
const metricName =
|
|
436
|
+
const metricName = BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE;
|
|
443
437
|
const data = {
|
|
444
438
|
correlation_id: meetingId,
|
|
445
439
|
reason: error.message,
|
|
@@ -449,7 +443,7 @@ pc.createOffer = (peerConnection, {
|
|
|
449
443
|
type: error.name
|
|
450
444
|
};
|
|
451
445
|
|
|
452
|
-
Metrics.
|
|
446
|
+
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
453
447
|
}
|
|
454
448
|
|
|
455
449
|
Metrics.postEvent({
|
|
@@ -544,15 +538,15 @@ pc.createAnswer = (params, {meetingId, remoteQualityLevel}) => {
|
|
|
544
538
|
})
|
|
545
539
|
.catch((error) => {
|
|
546
540
|
if (error instanceof InvalidSdpError) {
|
|
547
|
-
Metrics.
|
|
548
|
-
|
|
541
|
+
Metrics.sendBehavioralMetric(
|
|
542
|
+
BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
|
|
549
543
|
{
|
|
550
544
|
correlation_id: meetingId
|
|
551
545
|
}
|
|
552
546
|
);
|
|
553
547
|
}
|
|
554
548
|
else {
|
|
555
|
-
const metricName =
|
|
549
|
+
const metricName = BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE;
|
|
556
550
|
const data = {
|
|
557
551
|
correlation_id: meetingId,
|
|
558
552
|
reason: error.message,
|
|
@@ -562,7 +556,7 @@ pc.createAnswer = (params, {meetingId, remoteQualityLevel}) => {
|
|
|
562
556
|
type: error.name
|
|
563
557
|
};
|
|
564
558
|
|
|
565
|
-
Metrics.
|
|
559
|
+
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
566
560
|
}
|
|
567
561
|
|
|
568
562
|
LoggerProxy.logger.error(`PeerConnectionManager:index#setRemoteSessionDetails --> Error creating remote session, error: ${error}`);
|
|
@@ -625,8 +619,8 @@ pc.setPeerConnectionEvents = (meeting) => {
|
|
|
625
619
|
function: 'connectionFailed'
|
|
626
620
|
});
|
|
627
621
|
|
|
628
|
-
Metrics.
|
|
629
|
-
|
|
622
|
+
Metrics.sendBehavioralMetric(
|
|
623
|
+
BEHAVIORAL_METRICS.CONNECTION_FAILURE,
|
|
630
624
|
{
|
|
631
625
|
correlation_id: meeting.correlationId,
|
|
632
626
|
locus_id: meeting.locusId
|
|
@@ -648,6 +642,13 @@ pc.setPeerConnectionEvents = (meeting) => {
|
|
|
648
642
|
// Ice connection state goes to connected when both client and server sends STUN packets and
|
|
649
643
|
// Established connected between them. Firefox does not trigger COMPLETED and only trigger CONNECTED
|
|
650
644
|
Metrics.postEvent({event: eventType.ICE_END, meeting});
|
|
645
|
+
Metrics.sendBehavioralMetric(
|
|
646
|
+
BEHAVIORAL_METRICS.CONNECTION_SUCCESS,
|
|
647
|
+
{
|
|
648
|
+
correlation_id: meeting.correlationId,
|
|
649
|
+
locus_id: meeting.locusId
|
|
650
|
+
}
|
|
651
|
+
);
|
|
651
652
|
meeting.setNetworkStatus(NETWORK_STATUS.CONNECTED);
|
|
652
653
|
meeting.reconnectionManager.iceReconnected();
|
|
653
654
|
LoggerProxy.logger.info('PeerConnectionManager:index#setPeerConnectionEvents --> ICE STATE CONNECTED.');
|
|
@@ -8,7 +8,7 @@ import LoggerProxy from '../common/logs/logger-proxy';
|
|
|
8
8
|
import Trigger from '../common/events/trigger-proxy';
|
|
9
9
|
import {
|
|
10
10
|
EVENT_TRIGGERS,
|
|
11
|
-
|
|
11
|
+
BEHAVIORAL_METRICS,
|
|
12
12
|
RECONNECTION,
|
|
13
13
|
SHARE_STATUS,
|
|
14
14
|
SHARE_STOPPED_REASON,
|
|
@@ -424,8 +424,8 @@ export default class ReconnectionManager {
|
|
|
424
424
|
}
|
|
425
425
|
else {
|
|
426
426
|
LoggerProxy.logger.error('ReconnectionManager:index#rejoinMeeting --> Unable to rejoin meeting after max attempts.', joinError);
|
|
427
|
-
Metrics.
|
|
428
|
-
|
|
427
|
+
Metrics.sendBehavioralMetric(
|
|
428
|
+
BEHAVIORAL_METRICS.MEETING_MAX_REJOIN_FAILURE,
|
|
429
429
|
{
|
|
430
430
|
locus_id: this.meeting.locusUrl.split('/').pop(),
|
|
431
431
|
reason: joinError.message,
|
package/src/roap/handler.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {StatelessWebexPlugin} from '@webex/webex-core';
|
|
3
3
|
|
|
4
4
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
5
|
-
import {ROAP, _OFFER_,
|
|
5
|
+
import {ROAP, _OFFER_, BEHAVIORAL_METRICS} from '../constants';
|
|
6
6
|
import Metrics from '../metrics';
|
|
7
7
|
|
|
8
8
|
import RoapUtil from './util';
|
|
@@ -44,14 +44,14 @@ const handleSessionStep = ({
|
|
|
44
44
|
if (session.OFFER && messageType === _OFFER_) {
|
|
45
45
|
session.GLARE_OFFER = roap.msg;
|
|
46
46
|
session.GLARE_OFFER.remote = !!roap.remote;
|
|
47
|
-
const metricName =
|
|
47
|
+
const metricName = BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION;
|
|
48
48
|
const data = {
|
|
49
49
|
correlation_id: correlationId,
|
|
50
50
|
locus_id: locusUrl.split('/').pop(),
|
|
51
51
|
sequence: sequenceId
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
Metrics.
|
|
54
|
+
Metrics.sendBehavioralMetric(metricName, data);
|
|
55
55
|
|
|
56
56
|
LoggerProxy.logger.warn(`Roap:handler#handleSessionStep --> Glare condition occurred with new mercury event, sequenceId: ${sequenceId}`);
|
|
57
57
|
}
|
|
@@ -112,7 +112,7 @@ export default class RoapHandler extends StatelessWebexPlugin {
|
|
|
112
112
|
});
|
|
113
113
|
})
|
|
114
114
|
.catch((error) => {
|
|
115
|
-
const metricName =
|
|
115
|
+
const metricName = BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE;
|
|
116
116
|
const data = {
|
|
117
117
|
correlation_id: meeting.correlationId,
|
|
118
118
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
@@ -123,7 +123,7 @@ export default class RoapHandler extends StatelessWebexPlugin {
|
|
|
123
123
|
type: error.name
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
-
Metrics.
|
|
126
|
+
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
127
127
|
LoggerProxy.logger.error(`Roap:handler#perform --> Error occured during wait receive answer, continuing, ${error}`);
|
|
128
128
|
});
|
|
129
129
|
}
|
|
@@ -30,7 +30,7 @@ import Metrics from '@webex/plugin-meetings/src/metrics';
|
|
|
30
30
|
import {
|
|
31
31
|
FLOOR_ACTION,
|
|
32
32
|
SHARE_STATUS,
|
|
33
|
-
|
|
33
|
+
BEHAVIORAL_METRICS,
|
|
34
34
|
MEETING_INFO_FAILURE_REASON,
|
|
35
35
|
PASSWORD_STATUS,
|
|
36
36
|
EVENTS,
|
|
@@ -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
|
|
|
@@ -649,9 +662,10 @@ describe('plugin-meetings', () => {
|
|
|
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;
|
|
@@ -2401,12 +2433,12 @@ describe('plugin-meetings', () => {
|
|
|
2401
2433
|
});
|
|
2402
2434
|
|
|
2403
2435
|
it('should send metrics on reconnect failure', async () => {
|
|
2404
|
-
sandbox.stub(Metrics, '
|
|
2436
|
+
sandbox.stub(Metrics, 'sendBehavioralMetric');
|
|
2405
2437
|
await assert.isRejected(meeting.reconnect());
|
|
2406
|
-
assert(Metrics.
|
|
2438
|
+
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
2407
2439
|
assert.calledWith(
|
|
2408
|
-
Metrics.
|
|
2409
|
-
|
|
2440
|
+
Metrics.sendBehavioralMetric,
|
|
2441
|
+
BEHAVIORAL_METRICS.MEETING_RECONNECT_FAILURE,
|
|
2410
2442
|
{
|
|
2411
2443
|
correlation_id: meeting.correlationId,
|
|
2412
2444
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
@@ -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 () => {
|
|
@@ -90,9 +90,9 @@ browserOnly(describe)('Meeting metrics', () => {
|
|
|
90
90
|
});
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
describe('#
|
|
93
|
+
describe('#sendBehavioralMetric', () => {
|
|
94
94
|
it('sends client metric via Metrics plugin', () => {
|
|
95
|
-
metrics.
|
|
95
|
+
metrics.sendBehavioralMetric('myMetric');
|
|
96
96
|
|
|
97
97
|
assert.calledOnce(mockSubmitMetric);
|
|
98
98
|
});
|
|
@@ -101,13 +101,13 @@ browserOnly(describe)('Meeting metrics', () => {
|
|
|
101
101
|
const data = {value: 567};
|
|
102
102
|
const metadata = {test: true};
|
|
103
103
|
|
|
104
|
-
metrics.
|
|
104
|
+
metrics.sendBehavioralMetric('myMetric', data, metadata);
|
|
105
105
|
|
|
106
106
|
assert.calledWithMatch(
|
|
107
107
|
mockSubmitMetric,
|
|
108
108
|
'myMetric',
|
|
109
109
|
{
|
|
110
|
-
type: ['
|
|
110
|
+
type: ['behavioral'],
|
|
111
111
|
fields: {
|
|
112
112
|
browser_version: getBrowserVersion(),
|
|
113
113
|
os_version: getOSVersion(),
|
|
@@ -127,8 +127,8 @@ browserOnly(describe)('Meeting metrics', () => {
|
|
|
127
127
|
|
|
128
128
|
it('throws error if no metric name is given', () => {
|
|
129
129
|
assert.throws(
|
|
130
|
-
() => metrics.
|
|
131
|
-
'Missing
|
|
130
|
+
() => metrics.sendBehavioralMetric(),
|
|
131
|
+
'Missing behavioral metric name. Please provide one'
|
|
132
132
|
);
|
|
133
133
|
});
|
|
134
134
|
});
|