@webex/plugin-meetings 3.0.0-beta.74 → 3.0.0-beta.76
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 +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/meeting/index.js +36 -9
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +60 -22
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +2 -1
- package/dist/meetings/index.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/receiveSlotManager.js +16 -0
- package/dist/multistream/receiveSlotManager.js.map +1 -1
- package/dist/statsAnalyzer/index.js +32 -23
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/meeting/index.d.ts +1 -0
- package/dist/types/meeting-info/meeting-info-v2.d.ts +16 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/multistream/receiveSlotManager.d.ts +7 -0
- package/dist/types/statsAnalyzer/index.d.ts +6 -1
- package/package.json +18 -18
- package/src/constants.ts +1 -0
- package/src/meeting/index.ts +32 -3
- package/src/meeting-info/meeting-info-v2.ts +37 -0
- package/src/meetings/index.ts +6 -1
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/receiveSlotManager.ts +12 -0
- package/src/statsAnalyzer/index.ts +54 -23
- package/test/unit/spec/meeting/index.js +32 -0
- package/test/unit/spec/meeting-info/meetinginfov2.js +47 -0
- package/test/unit/spec/meetings/index.js +61 -0
- package/test/unit/spec/multistream/receiveSlotManager.ts +11 -3
- package/test/unit/spec/stats-analyzer/index.js +7 -2
package/src/meeting/index.ts
CHANGED
|
@@ -92,6 +92,7 @@ import MediaError from '../common/errors/media';
|
|
|
92
92
|
import {
|
|
93
93
|
MeetingInfoV2PasswordError,
|
|
94
94
|
MeetingInfoV2CaptchaError,
|
|
95
|
+
MeetingInfoV2PolicyError,
|
|
95
96
|
} from '../meeting-info/meeting-info-v2';
|
|
96
97
|
import BrowserDetection from '../common/browser-detection';
|
|
97
98
|
import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
@@ -113,6 +114,7 @@ import InMeetingActions from './in-meeting-actions';
|
|
|
113
114
|
import {REACTION_RELAY_TYPES} from '../reactions/constants';
|
|
114
115
|
import RecordingController from '../recording-controller';
|
|
115
116
|
import ControlsOptionsManager from '../controls-options-manager';
|
|
117
|
+
import PermissionError from '../common/errors/permission';
|
|
116
118
|
|
|
117
119
|
const {isBrowser} = BrowserDetection();
|
|
118
120
|
|
|
@@ -484,6 +486,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
484
486
|
};
|
|
485
487
|
|
|
486
488
|
meetingInfoFailureReason: string;
|
|
489
|
+
meetingInfoFailureCode?: number;
|
|
487
490
|
networkQualityMonitor: NetworkQualityMonitor;
|
|
488
491
|
networkStatus: string;
|
|
489
492
|
passwordStatus: string;
|
|
@@ -1084,6 +1087,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1084
1087
|
*/
|
|
1085
1088
|
this.meetingInfoFailureReason = undefined;
|
|
1086
1089
|
|
|
1090
|
+
/**
|
|
1091
|
+
* The numeric code, if any, associated with the last failure to obtain the meeting info
|
|
1092
|
+
* @instance
|
|
1093
|
+
* @type {number}
|
|
1094
|
+
* @private
|
|
1095
|
+
* @memberof Meeting
|
|
1096
|
+
*/
|
|
1097
|
+
this.meetingInfoFailureCode = undefined;
|
|
1098
|
+
|
|
1087
1099
|
/**
|
|
1088
1100
|
* Repeating timer used to send keepAlives when in lobby
|
|
1089
1101
|
* @instance
|
|
@@ -1203,7 +1215,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1203
1215
|
|
|
1204
1216
|
return Promise.resolve();
|
|
1205
1217
|
} catch (err) {
|
|
1206
|
-
if (err instanceof
|
|
1218
|
+
if (err instanceof MeetingInfoV2PolicyError) {
|
|
1219
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.POLICY;
|
|
1220
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1221
|
+
|
|
1222
|
+
if (err.meetingInfo) {
|
|
1223
|
+
this.meetingInfo = err.meetingInfo;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
throw new PermissionError();
|
|
1227
|
+
} else if (err instanceof MeetingInfoV2PasswordError) {
|
|
1207
1228
|
LoggerProxy.logger.info(
|
|
1208
1229
|
// @ts-ignore
|
|
1209
1230
|
`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - password required (code=${err?.body?.code}).`
|
|
@@ -1215,6 +1236,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1215
1236
|
this.meetingNumber = err.meetingInfo.meetingNumber;
|
|
1216
1237
|
}
|
|
1217
1238
|
|
|
1239
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1240
|
+
|
|
1218
1241
|
this.passwordStatus = PASSWORD_STATUS.REQUIRED;
|
|
1219
1242
|
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
|
|
1220
1243
|
if (this.requiredCaptcha) {
|
|
@@ -1233,6 +1256,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1233
1256
|
? MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA
|
|
1234
1257
|
: MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
|
|
1235
1258
|
|
|
1259
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1260
|
+
|
|
1236
1261
|
if (err.isPasswordRequired) {
|
|
1237
1262
|
this.passwordStatus = PASSWORD_STATUS.REQUIRED;
|
|
1238
1263
|
}
|
|
@@ -5318,8 +5343,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5318
5343
|
if (this.config.stats.enableStatsAnalyzer) {
|
|
5319
5344
|
// @ts-ignore - config coming from registerPlugin
|
|
5320
5345
|
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
|
5321
|
-
|
|
5322
|
-
|
|
5346
|
+
this.statsAnalyzer = new StatsAnalyzer(
|
|
5347
|
+
// @ts-ignore - config coming from registerPlugin
|
|
5348
|
+
this.config.stats,
|
|
5349
|
+
(ssrc: number) => this.receiveSlotManager.findReceiveSlotBySsrc(ssrc),
|
|
5350
|
+
this.networkQualityMonitor
|
|
5351
|
+
);
|
|
5323
5352
|
this.setupStatsAnalyzerEventHandlers();
|
|
5324
5353
|
this.networkQualityMonitor.on(
|
|
5325
5354
|
EVENT_TRIGGERS.NETWORK_QUALITY,
|
|
@@ -17,6 +17,7 @@ const CAPTCHA_ERROR_DEFAULT_MESSAGE =
|
|
|
17
17
|
const ADHOC_MEETING_DEFAULT_ERROR =
|
|
18
18
|
'Failed starting the adhoc meeting, Please contact support team ';
|
|
19
19
|
const CAPTCHA_ERROR_REQUIRES_PASSWORD_CODES = [423005, 423006];
|
|
20
|
+
const POLICY_ERROR_CODES = [403049, 403104, 403103, 403048, 403102, 403101];
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Error to indicate that wbxappapi requires a password
|
|
@@ -69,6 +70,30 @@ export class MeetingInfoV2AdhocMeetingError extends Error {
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Error preventing join because of a meeting policy
|
|
75
|
+
*/
|
|
76
|
+
export class MeetingInfoV2PolicyError extends Error {
|
|
77
|
+
meetingInfo: object;
|
|
78
|
+
sdkMessage: string;
|
|
79
|
+
wbxAppApiCode: number;
|
|
80
|
+
/**
|
|
81
|
+
*
|
|
82
|
+
* @constructor
|
|
83
|
+
* @param {Number} [wbxAppApiErrorCode]
|
|
84
|
+
* @param {Object} [meetingInfo]
|
|
85
|
+
* @param {String} [message]
|
|
86
|
+
*/
|
|
87
|
+
constructor(wbxAppApiErrorCode?: number, meetingInfo?: object, message?: string) {
|
|
88
|
+
super(`${message}, code=${wbxAppApiErrorCode}`);
|
|
89
|
+
this.name = 'MeetingInfoV2AdhocMeetingError';
|
|
90
|
+
this.sdkMessage = message;
|
|
91
|
+
this.stack = new Error().stack;
|
|
92
|
+
this.wbxAppApiCode = wbxAppApiErrorCode;
|
|
93
|
+
this.meetingInfo = meetingInfo;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
72
97
|
/**
|
|
73
98
|
* Error to indicate that preferred webex site not present to start adhoc meeting
|
|
74
99
|
*/
|
|
@@ -271,6 +296,18 @@ export default class MeetingInfoV2 {
|
|
|
271
296
|
})
|
|
272
297
|
.catch((err) => {
|
|
273
298
|
if (err?.statusCode === 403) {
|
|
299
|
+
if (POLICY_ERROR_CODES.includes(err.body?.code)) {
|
|
300
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_INFO_POLICY_ERROR, {
|
|
301
|
+
code: err.body?.code,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
throw new MeetingInfoV2PolicyError(
|
|
305
|
+
err.body?.code,
|
|
306
|
+
err.body?.data?.meetingInfo,
|
|
307
|
+
err.body?.message
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
274
311
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.VERIFY_PASSWORD_ERROR, {
|
|
275
312
|
reason: err.message,
|
|
276
313
|
stack: err.stack,
|
package/src/meetings/index.ts
CHANGED
|
@@ -57,6 +57,7 @@ import CaptchaError from '../common/errors/captcha-error';
|
|
|
57
57
|
|
|
58
58
|
import MeetingCollection from './collection';
|
|
59
59
|
import MeetingsUtil from './util';
|
|
60
|
+
import PermissionError from '../common/errors/permission';
|
|
60
61
|
|
|
61
62
|
let mediaLogger;
|
|
62
63
|
|
|
@@ -1115,7 +1116,11 @@ export default class Meetings extends WebexPlugin {
|
|
|
1115
1116
|
await meeting.fetchMeetingInfo({});
|
|
1116
1117
|
}
|
|
1117
1118
|
} catch (err) {
|
|
1118
|
-
if (
|
|
1119
|
+
if (
|
|
1120
|
+
!(err instanceof CaptchaError) &&
|
|
1121
|
+
!(err instanceof PasswordError) &&
|
|
1122
|
+
!(err instanceof PermissionError)
|
|
1123
|
+
) {
|
|
1119
1124
|
// if there is no meeting info we assume its a 1:1 call or wireless share
|
|
1120
1125
|
LoggerProxy.logger.info(
|
|
1121
1126
|
`Meetings:index#createMeeting --> Info Unable to fetch meeting info for ${destination}.`
|
package/src/metrics/constants.ts
CHANGED
|
@@ -52,6 +52,7 @@ const BEHAVIORAL_METRICS = {
|
|
|
52
52
|
MOVE_FROM_SUCCESS: 'js_sdk_move_from_success',
|
|
53
53
|
MOVE_FROM_FAILURE: 'js_sdk_move_from_failure',
|
|
54
54
|
TURN_DISCOVERY_FAILURE: 'js_sdk_turn_discovery_failure',
|
|
55
|
+
MEETING_INFO_POLICY_ERROR: 'js_sdk_meeting_info_policy_error',
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
export {BEHAVIORAL_METRICS as default};
|
|
@@ -151,4 +151,16 @@ export class ReceiveSlotManager {
|
|
|
151
151
|
});
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Find a receive slot by a ssrc.
|
|
157
|
+
*
|
|
158
|
+
* @param ssrc - The ssrc of the receive slot to find.
|
|
159
|
+
* @returns - The receive slot with this ssrc, undefined if not found.
|
|
160
|
+
*/
|
|
161
|
+
findReceiveSlotBySsrc(ssrc: number): ReceiveSlot | undefined {
|
|
162
|
+
return Object.values(this.allocatedSlots)
|
|
163
|
+
.flat()
|
|
164
|
+
.find((r) => ssrc && r.wcmeReceiveSlot?.id?.ssrc === ssrc);
|
|
165
|
+
}
|
|
154
166
|
}
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
getVideoSenderMqa,
|
|
29
29
|
getVideoReceiverMqa,
|
|
30
30
|
} from './mqaUtil';
|
|
31
|
+
import {ReceiveSlot} from '../multistream/receiveSlot';
|
|
31
32
|
|
|
32
33
|
export const EVENTS = {
|
|
33
34
|
MEDIA_QUALITY: 'MEDIA_QUALITY',
|
|
@@ -53,6 +54,8 @@ const emptyReceiver = {
|
|
|
53
54
|
meanRoundTripTime: [],
|
|
54
55
|
};
|
|
55
56
|
|
|
57
|
+
type ReceiveSlotCallback = (csi: number) => ReceiveSlot | undefined;
|
|
58
|
+
|
|
56
59
|
/**
|
|
57
60
|
* Stats Analyzer class that will emit events based on detected quality
|
|
58
61
|
*
|
|
@@ -74,17 +77,20 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
74
77
|
statsInterval: NodeJS.Timeout;
|
|
75
78
|
statsResults: any;
|
|
76
79
|
statsStarted: any;
|
|
80
|
+
receiveSlotCallback: ReceiveSlotCallback;
|
|
77
81
|
|
|
78
82
|
/**
|
|
79
83
|
* Creates a new instance of StatsAnalyzer
|
|
80
84
|
* @constructor
|
|
81
85
|
* @public
|
|
82
86
|
* @param {Object} config SDK Configuration Object
|
|
87
|
+
* @param {Function} receiveSlotCallback Callback used to access receive slots.
|
|
83
88
|
* @param {Object} networkQualityMonitor class for assessing network characteristics (jitter, packetLoss, latency)
|
|
84
89
|
* @param {Object} statsResults Default properties for stats
|
|
85
90
|
*/
|
|
86
91
|
constructor(
|
|
87
92
|
config: any,
|
|
93
|
+
receiveSlotCallback: ReceiveSlotCallback = () => undefined,
|
|
88
94
|
networkQualityMonitor: object = {},
|
|
89
95
|
statsResults: object = defaultStats
|
|
90
96
|
) {
|
|
@@ -98,6 +104,7 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
98
104
|
this.mqaSentCount = -1;
|
|
99
105
|
this.lastMqaDataSent = {};
|
|
100
106
|
this.lastEmittedStartStopEvent = {};
|
|
107
|
+
this.receiveSlotCallback = receiveSlotCallback;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
/**
|
|
@@ -523,7 +530,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
523
530
|
currentStats.totalPacketsSent === 0
|
|
524
531
|
) {
|
|
525
532
|
LoggerProxy.logger.info(
|
|
526
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent
|
|
533
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets sent`,
|
|
534
|
+
currentStats.totalPacketsSent
|
|
527
535
|
);
|
|
528
536
|
} else {
|
|
529
537
|
if (
|
|
@@ -531,7 +539,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
531
539
|
currentStats.totalAudioEnergy === 0
|
|
532
540
|
) {
|
|
533
541
|
LoggerProxy.logger.info(
|
|
534
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No audio Energy present
|
|
542
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No audio Energy present`,
|
|
543
|
+
currentStats.totalAudioEnergy
|
|
535
544
|
);
|
|
536
545
|
}
|
|
537
546
|
|
|
@@ -565,14 +574,16 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
565
574
|
|
|
566
575
|
if (currentPacketsReceived === previousPacketsReceived || currentPacketsReceived === 0) {
|
|
567
576
|
LoggerProxy.logger.info(
|
|
568
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets received
|
|
577
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets received`,
|
|
578
|
+
currentPacketsReceived
|
|
569
579
|
);
|
|
570
580
|
} else if (
|
|
571
581
|
currentSamplesReceived === previousSamplesReceived ||
|
|
572
582
|
currentSamplesReceived === 0
|
|
573
583
|
) {
|
|
574
584
|
LoggerProxy.logger.info(
|
|
575
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No audio samples received
|
|
585
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No audio samples received`,
|
|
586
|
+
currentSamplesReceived
|
|
576
587
|
);
|
|
577
588
|
}
|
|
578
589
|
|
|
@@ -589,7 +600,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
589
600
|
currentStats.totalPacketsSent === 0
|
|
590
601
|
) {
|
|
591
602
|
LoggerProxy.logger.info(
|
|
592
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent
|
|
603
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent`,
|
|
604
|
+
currentStats.totalPacketsSent
|
|
593
605
|
);
|
|
594
606
|
} else {
|
|
595
607
|
if (
|
|
@@ -597,7 +609,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
597
609
|
currentStats.framesEncoded === 0
|
|
598
610
|
) {
|
|
599
611
|
LoggerProxy.logger.info(
|
|
600
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video Frames Encoded
|
|
612
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video Frames Encoded`,
|
|
613
|
+
currentStats.framesEncoded
|
|
601
614
|
);
|
|
602
615
|
}
|
|
603
616
|
|
|
@@ -607,7 +620,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
607
620
|
this.statsResults.resolutions['video-send'].send.framesSent === 0
|
|
608
621
|
) {
|
|
609
622
|
LoggerProxy.logger.info(
|
|
610
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video Frames sent
|
|
623
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video Frames sent`,
|
|
624
|
+
this.statsResults.resolutions['video-send'].send.framesSent
|
|
611
625
|
);
|
|
612
626
|
}
|
|
613
627
|
}
|
|
@@ -643,24 +657,28 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
643
657
|
|
|
644
658
|
if (currentPacketsReceived === previousPacketsReceived || currentPacketsReceived === 0) {
|
|
645
659
|
LoggerProxy.logger.info(
|
|
646
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets received
|
|
660
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets received`,
|
|
661
|
+
currentPacketsReceived
|
|
647
662
|
);
|
|
648
663
|
} else {
|
|
649
664
|
if (currentFramesReceived === previousFramesReceived || currentFramesReceived === 0) {
|
|
650
665
|
LoggerProxy.logger.info(
|
|
651
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video frames received
|
|
666
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video frames received`,
|
|
667
|
+
currentFramesReceived
|
|
652
668
|
);
|
|
653
669
|
}
|
|
654
670
|
|
|
655
671
|
if (currentFramesDecoded === previousFramesDecoded || currentFramesDecoded === 0) {
|
|
656
672
|
LoggerProxy.logger.info(
|
|
657
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No video frames decoded
|
|
673
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No video frames decoded`,
|
|
674
|
+
currentFramesDecoded
|
|
658
675
|
);
|
|
659
676
|
}
|
|
660
677
|
|
|
661
678
|
if (currentFramesDropped - previousFramesDropped > 10) {
|
|
662
679
|
LoggerProxy.logger.info(
|
|
663
|
-
`StatsAnalyzer:index#compareLastStatsResult --> video frames are getting dropped
|
|
680
|
+
`StatsAnalyzer:index#compareLastStatsResult --> video frames are getting dropped`,
|
|
681
|
+
currentFramesDropped - previousFramesDropped
|
|
664
682
|
);
|
|
665
683
|
}
|
|
666
684
|
}
|
|
@@ -679,7 +697,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
679
697
|
currentStats.totalPacketsSent === 0
|
|
680
698
|
) {
|
|
681
699
|
LoggerProxy.logger.info(
|
|
682
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent
|
|
700
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent`,
|
|
701
|
+
currentStats.totalPacketsSent
|
|
683
702
|
);
|
|
684
703
|
} else {
|
|
685
704
|
if (
|
|
@@ -687,7 +706,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
687
706
|
currentStats.framesEncoded === 0
|
|
688
707
|
) {
|
|
689
708
|
LoggerProxy.logger.info(
|
|
690
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share frames getting encoded
|
|
709
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share frames getting encoded`,
|
|
710
|
+
currentStats.framesEncoded
|
|
691
711
|
);
|
|
692
712
|
}
|
|
693
713
|
|
|
@@ -697,7 +717,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
697
717
|
this.statsResults.resolutions['video-share-send'].send.framesSent === 0
|
|
698
718
|
) {
|
|
699
719
|
LoggerProxy.logger.info(
|
|
700
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share frames sent
|
|
720
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share frames sent`,
|
|
721
|
+
this.statsResults.resolutions['video-share-send'].send.framesSent
|
|
701
722
|
);
|
|
702
723
|
}
|
|
703
724
|
}
|
|
@@ -735,24 +756,28 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
735
756
|
|
|
736
757
|
if (currentPacketsReceived === previousPacketsReceived || currentPacketsReceived === 0) {
|
|
737
758
|
LoggerProxy.logger.info(
|
|
738
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets received
|
|
759
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets received`,
|
|
760
|
+
currentPacketsReceived
|
|
739
761
|
);
|
|
740
762
|
} else {
|
|
741
763
|
if (currentFramesReceived === previousFramesReceived || currentFramesReceived === 0) {
|
|
742
764
|
LoggerProxy.logger.info(
|
|
743
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share frames received
|
|
765
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share frames received`,
|
|
766
|
+
currentFramesReceived
|
|
744
767
|
);
|
|
745
768
|
}
|
|
746
769
|
|
|
747
770
|
if (currentFramesDecoded === previousFramesDecoded || currentFramesDecoded === 0) {
|
|
748
771
|
LoggerProxy.logger.info(
|
|
749
|
-
`StatsAnalyzer:index#compareLastStatsResult --> No share frames decoded
|
|
772
|
+
`StatsAnalyzer:index#compareLastStatsResult --> No share frames decoded`,
|
|
773
|
+
currentFramesDecoded
|
|
750
774
|
);
|
|
751
775
|
}
|
|
752
776
|
|
|
753
777
|
if (currentFramesDropped - previousFramesDropped > 10) {
|
|
754
778
|
LoggerProxy.logger.info(
|
|
755
|
-
`StatsAnalyzer:index#compareLastStatsResult --> share frames are getting dropped
|
|
779
|
+
`StatsAnalyzer:index#compareLastStatsResult --> share frames are getting dropped`,
|
|
780
|
+
currentFramesDropped - previousFramesDropped
|
|
756
781
|
);
|
|
757
782
|
}
|
|
758
783
|
}
|
|
@@ -933,6 +958,10 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
933
958
|
|
|
934
959
|
if (result.bytesReceived) {
|
|
935
960
|
let kilobytes = 0;
|
|
961
|
+
const receiveSlot = this.receiveSlotCallback(result.ssrc);
|
|
962
|
+
const idAndCsi = receiveSlot
|
|
963
|
+
? `id: "${receiveSlot.id || ''}"${receiveSlot.csi ? ` and csi: ${receiveSlot.csi}` : ''}`
|
|
964
|
+
: '';
|
|
936
965
|
|
|
937
966
|
if (result.frameWidth && result.frameHeight) {
|
|
938
967
|
this.statsResults.resolutions[mediaType][sendrecvType].width = result.frameWidth;
|
|
@@ -989,10 +1018,12 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
989
1018
|
result.packetsReceived;
|
|
990
1019
|
|
|
991
1020
|
if (this.statsResults[mediaType][sendrecvType].packetsReceived === 0) {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1021
|
+
if (receiveSlot) {
|
|
1022
|
+
LoggerProxy.logger.info(
|
|
1023
|
+
`StatsAnalyzer:index#processInboundRTPResult --> No packets received for receive slot ${idAndCsi}`,
|
|
1024
|
+
this.statsResults[mediaType][sendrecvType].packetsReceived
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
996
1027
|
}
|
|
997
1028
|
|
|
998
1029
|
// Check the over all packet Lost ratio
|
|
@@ -1004,7 +1035,7 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
1004
1035
|
: 0;
|
|
1005
1036
|
if (this.statsResults[mediaType][sendrecvType].currentPacketLossRatio > 3) {
|
|
1006
1037
|
LoggerProxy.logger.info(
|
|
1007
|
-
|
|
1038
|
+
`StatsAnalyzer:index#processInboundRTPResult --> Packets getting lost from the receiver with slot ${idAndCsi}`,
|
|
1008
1039
|
this.statsResults[mediaType][sendrecvType].currentPacketLossRatio
|
|
1009
1040
|
);
|
|
1010
1041
|
}
|
|
@@ -73,12 +73,14 @@ import WebExMeetingsErrors from '../../../../src/common/errors/webex-meetings-er
|
|
|
73
73
|
import ParameterError from '../../../../src/common/errors/parameter';
|
|
74
74
|
import PasswordError from '../../../../src/common/errors/password-error';
|
|
75
75
|
import CaptchaError from '../../../../src/common/errors/captcha-error';
|
|
76
|
+
import PermissionError from '../../../../src/common/errors/permission';
|
|
76
77
|
import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
|
|
77
78
|
import DefaultSDKConfig from '../../../../src/config';
|
|
78
79
|
import testUtils from '../../../utils/testUtils';
|
|
79
80
|
import {
|
|
80
81
|
MeetingInfoV2CaptchaError,
|
|
81
82
|
MeetingInfoV2PasswordError,
|
|
83
|
+
MeetingInfoV2PolicyError,
|
|
82
84
|
} from '../../../../src/meeting-info/meeting-info-v2';
|
|
83
85
|
|
|
84
86
|
const {getBrowserName} = BrowserDetection();
|
|
@@ -3107,6 +3109,7 @@ describe('plugin-meetings', () => {
|
|
|
3107
3109
|
);
|
|
3108
3110
|
|
|
3109
3111
|
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
3112
|
+
assert.equal(meeting.meetingInfoFailureCode, 403004);
|
|
3110
3113
|
assert.equal(
|
|
3111
3114
|
meeting.meetingInfoFailureReason,
|
|
3112
3115
|
MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD
|
|
@@ -3115,6 +3118,34 @@ describe('plugin-meetings', () => {
|
|
|
3115
3118
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
|
|
3116
3119
|
});
|
|
3117
3120
|
|
|
3121
|
+
it('handles meetingInfoProvider policy error', async () => {
|
|
3122
|
+
meeting.destination = FAKE_DESTINATION;
|
|
3123
|
+
meeting.destinationType = FAKE_TYPE;
|
|
3124
|
+
meeting.attrs.meetingInfoProvider = {
|
|
3125
|
+
fetchMeetingInfo: sinon
|
|
3126
|
+
.stub()
|
|
3127
|
+
.throws(new MeetingInfoV2PolicyError(123456, FAKE_MEETING_INFO, 'a message')),
|
|
3128
|
+
};
|
|
3129
|
+
|
|
3130
|
+
await assert.isRejected(meeting.fetchMeetingInfo({}), PermissionError);
|
|
3131
|
+
|
|
3132
|
+
assert.calledWith(
|
|
3133
|
+
meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
|
|
3134
|
+
FAKE_DESTINATION,
|
|
3135
|
+
FAKE_TYPE,
|
|
3136
|
+
null,
|
|
3137
|
+
null
|
|
3138
|
+
);
|
|
3139
|
+
|
|
3140
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
3141
|
+
assert.equal(meeting.meetingInfoFailureCode, 123456);
|
|
3142
|
+
assert.equal(
|
|
3143
|
+
meeting.meetingInfoFailureReason,
|
|
3144
|
+
MEETING_INFO_FAILURE_REASON.POLICY
|
|
3145
|
+
);
|
|
3146
|
+
});
|
|
3147
|
+
|
|
3148
|
+
|
|
3118
3149
|
it('handles meetingInfoProvider requiring captcha because of wrong password', async () => {
|
|
3119
3150
|
meeting.destination = FAKE_DESTINATION;
|
|
3120
3151
|
meeting.destinationType = FAKE_TYPE;
|
|
@@ -3145,6 +3176,7 @@ describe('plugin-meetings', () => {
|
|
|
3145
3176
|
meeting.meetingInfoFailureReason,
|
|
3146
3177
|
MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD
|
|
3147
3178
|
);
|
|
3179
|
+
assert.equal(meeting.meetingInfoFailureCode, 423005);
|
|
3148
3180
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
|
|
3149
3181
|
assert.deepEqual(meeting.requiredCaptcha, {
|
|
3150
3182
|
captchaId: FAKE_CAPTCHA_ID,
|
|
@@ -21,10 +21,12 @@ import MeetingInfo, {
|
|
|
21
21
|
MeetingInfoV2PasswordError,
|
|
22
22
|
MeetingInfoV2CaptchaError,
|
|
23
23
|
MeetingInfoV2AdhocMeetingError,
|
|
24
|
+
MeetingInfoV2PolicyError,
|
|
24
25
|
} from '@webex/plugin-meetings/src/meeting-info/meeting-info-v2';
|
|
25
26
|
import MeetingInfoUtil from '@webex/plugin-meetings/src/meeting-info/utilv2';
|
|
26
27
|
import Metrics from '@webex/plugin-meetings/src/metrics';
|
|
27
28
|
import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
|
|
29
|
+
import { forEach } from 'lodash';
|
|
28
30
|
|
|
29
31
|
describe('plugin-meetings', () => {
|
|
30
32
|
const conversation = {
|
|
@@ -286,6 +288,51 @@ describe('plugin-meetings', () => {
|
|
|
286
288
|
}
|
|
287
289
|
});
|
|
288
290
|
|
|
291
|
+
forEach(
|
|
292
|
+
[
|
|
293
|
+
{errorCode: 403049},
|
|
294
|
+
{errorCode: 403104},
|
|
295
|
+
{errorCode: 403103},
|
|
296
|
+
{errorCode: 403048},
|
|
297
|
+
{errorCode: 403102},
|
|
298
|
+
{errorCode: 403101},
|
|
299
|
+
],
|
|
300
|
+
({errorCode}) => {
|
|
301
|
+
it(`should throw a MeetingInfoV2PolicyError for error code ${errorCode}`, async () => {
|
|
302
|
+
const message = 'a message';
|
|
303
|
+
const meetingInfoData = 'meeting info';
|
|
304
|
+
|
|
305
|
+
webex.request = sinon
|
|
306
|
+
.stub()
|
|
307
|
+
.rejects({
|
|
308
|
+
statusCode: 403,
|
|
309
|
+
body: {message, code: errorCode, data: {meetingInfo: meetingInfoData}},
|
|
310
|
+
});
|
|
311
|
+
try {
|
|
312
|
+
await meetingInfo.fetchMeetingInfo('1234323', _MEETING_ID_, 'abc', {
|
|
313
|
+
id: '999',
|
|
314
|
+
code: 'aabbcc11',
|
|
315
|
+
});
|
|
316
|
+
assert.fail('fetchMeetingInfo should have thrown, but has not done that');
|
|
317
|
+
} catch (err) {
|
|
318
|
+
assert.instanceOf(err, MeetingInfoV2PolicyError);
|
|
319
|
+
assert.deepEqual(
|
|
320
|
+
err.message,
|
|
321
|
+
`${message}, code=${errorCode}`
|
|
322
|
+
);
|
|
323
|
+
assert.equal(err.wbxAppApiCode, errorCode);
|
|
324
|
+
assert.deepEqual(err.meetingInfo, meetingInfoData);
|
|
325
|
+
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
326
|
+
assert.calledWith(
|
|
327
|
+
Metrics.sendBehavioralMetric,
|
|
328
|
+
BEHAVIORAL_METRICS.MEETING_INFO_POLICY_ERROR,
|
|
329
|
+
{code: errorCode}
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
|
|
289
336
|
it('should throw MeetingInfoV2PasswordError for 403 response', async () => {
|
|
290
337
|
const FAKE_MEETING_INFO = {blablabla: 'some_fake_meeting_info'};
|
|
291
338
|
|
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
LOCUSINFO,
|
|
31
31
|
EVENT_TRIGGERS,
|
|
32
32
|
} from '../../../../src/constants';
|
|
33
|
+
import CaptchaError from '@webex/plugin-meetings/src/common/errors/captcha-error';
|
|
34
|
+
import { forEach } from 'lodash';
|
|
35
|
+
import PasswordError from '@webex/plugin-meetings/src/common/errors/password-error';
|
|
36
|
+
import PermissionError from '@webex/plugin-meetings/src/common/errors/permission';
|
|
33
37
|
|
|
34
38
|
describe('plugin-meetings', () => {
|
|
35
39
|
const logger = {
|
|
@@ -1212,6 +1216,63 @@ describe('plugin-meetings', () => {
|
|
|
1212
1216
|
);
|
|
1213
1217
|
});
|
|
1214
1218
|
});
|
|
1219
|
+
|
|
1220
|
+
describe('rejected MeetingInfo.#fetchMeetingInfo - does not log for known Error types', () => {
|
|
1221
|
+
forEach(
|
|
1222
|
+
[
|
|
1223
|
+
{
|
|
1224
|
+
error: new CaptchaError(),
|
|
1225
|
+
debugLogMessage:
|
|
1226
|
+
'Meetings:index#createMeeting --> Debug CaptchaError: Captcha is required. fetching /meetingInfo for creation.',
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
error: new PasswordError(),
|
|
1230
|
+
debugLogMessage:
|
|
1231
|
+
'Meetings:index#createMeeting --> Debug PasswordError: Password is required, please use verifyPassword() fetching /meetingInfo for creation.',
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
error: new PermissionError(),
|
|
1235
|
+
debugLogMessage:
|
|
1236
|
+
'Meetings:index#createMeeting --> Debug PermissionError: Not allowed to execute the function, some properties on server, or local client state do not allow you to complete this action. fetching /meetingInfo for creation.',
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
error: new Error(),
|
|
1240
|
+
infoLogMessage: true,
|
|
1241
|
+
debugLogMessage:
|
|
1242
|
+
'Meetings:index#createMeeting --> Debug Error fetching /meetingInfo for creation.',
|
|
1243
|
+
},
|
|
1244
|
+
],
|
|
1245
|
+
({error, debugLogMessage, infoLogMessage}) => {
|
|
1246
|
+
it('creates the meeting from a rejected meeting info fetch', async () => {
|
|
1247
|
+
webex.meetings.meetingInfo.fetchMeetingInfo = sinon
|
|
1248
|
+
.stub()
|
|
1249
|
+
.returns(Promise.reject(error));
|
|
1250
|
+
|
|
1251
|
+
LoggerProxy.logger.debug = sinon.stub();
|
|
1252
|
+
LoggerProxy.logger.info = sinon.stub();
|
|
1253
|
+
|
|
1254
|
+
const meeting = await webex.meetings.createMeeting('test destination', 'test type');
|
|
1255
|
+
|
|
1256
|
+
assert.instanceOf(
|
|
1257
|
+
meeting,
|
|
1258
|
+
Meeting,
|
|
1259
|
+
'createMeeting should eventually resolve to a Meeting Object'
|
|
1260
|
+
);
|
|
1261
|
+
|
|
1262
|
+
assert.calledWith(LoggerProxy.logger.debug, debugLogMessage);
|
|
1263
|
+
|
|
1264
|
+
if (infoLogMessage) {
|
|
1265
|
+
assert.calledWith(
|
|
1266
|
+
LoggerProxy.logger.info,
|
|
1267
|
+
'Meetings:index#createMeeting --> Info Unable to fetch meeting info for test destination.'
|
|
1268
|
+
);
|
|
1269
|
+
} else {
|
|
1270
|
+
assert.notCalled(LoggerProxy.logger.info);
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
);
|
|
1275
|
+
});
|
|
1215
1276
|
});
|
|
1216
1277
|
});
|
|
1217
1278
|
describe('Public Event Triggers', () => {
|