@webex/plugin-meetings 2.19.1 → 2.19.2

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.
Files changed (60) hide show
  1. package/README.md +0 -300
  2. package/dist/constants.js +3 -206
  3. package/dist/constants.js.map +1 -1
  4. package/dist/meeting/index.js +352 -489
  5. package/dist/meeting/index.js.map +1 -1
  6. package/dist/meeting/util.js +4 -213
  7. package/dist/meeting/util.js.map +1 -1
  8. package/dist/meetings/index.js +0 -28
  9. package/dist/meetings/index.js.map +1 -1
  10. package/dist/statsAnalyzer/index.js +145 -86
  11. package/dist/statsAnalyzer/index.js.map +1 -1
  12. package/package.json +5 -7
  13. package/src/constants.ts +1 -214
  14. package/src/meeting/index.js +110 -208
  15. package/src/meeting/util.js +4 -252
  16. package/src/meetings/index.js +0 -22
  17. package/src/statsAnalyzer/index.js +164 -99
  18. package/test/integration/spec/journey.js +2 -67
  19. package/test/unit/spec/meeting/index.js +88 -29
  20. package/test/unit/spec/meeting/utils.js +0 -2
  21. package/test/unit/spec/stats-analyzer/index.js +209 -1
  22. package/dist/analyzer/analyzer.js +0 -113
  23. package/dist/analyzer/analyzer.js.map +0 -1
  24. package/dist/analyzer/calculator.js +0 -87
  25. package/dist/analyzer/calculator.js.map +0 -1
  26. package/dist/metrics/mqa-processor.js +0 -170
  27. package/dist/metrics/mqa-processor.js.map +0 -1
  28. package/dist/stats/data.js +0 -93
  29. package/dist/stats/data.js.map +0 -1
  30. package/dist/stats/events.js +0 -222
  31. package/dist/stats/events.js.map +0 -1
  32. package/dist/stats/filter.js +0 -84
  33. package/dist/stats/filter.js.map +0 -1
  34. package/dist/stats/history.js +0 -147
  35. package/dist/stats/history.js.map +0 -1
  36. package/dist/stats/index.js +0 -425
  37. package/dist/stats/index.js.map +0 -1
  38. package/dist/stats/metrics.js +0 -112
  39. package/dist/stats/metrics.js.map +0 -1
  40. package/dist/stats/stats.js +0 -592
  41. package/dist/stats/stats.js.map +0 -1
  42. package/dist/stats/stream.js +0 -156
  43. package/dist/stats/stream.js.map +0 -1
  44. package/dist/stats/transformer.js +0 -126
  45. package/dist/stats/transformer.js.map +0 -1
  46. package/dist/stats/util.js +0 -64
  47. package/dist/stats/util.js.map +0 -1
  48. package/src/analyzer/analyzer.js +0 -78
  49. package/src/analyzer/calculator.js +0 -77
  50. package/src/metrics/mqa-processor.js +0 -118
  51. package/src/stats/data.js +0 -56
  52. package/src/stats/events.js +0 -185
  53. package/src/stats/filter.js +0 -40
  54. package/src/stats/history.js +0 -107
  55. package/src/stats/index.js +0 -320
  56. package/src/stats/metrics.js +0 -95
  57. package/src/stats/stats.js +0 -477
  58. package/src/stats/stream.js +0 -108
  59. package/src/stats/transformer.js +0 -109
  60. package/src/stats/util.js +0 -44
@@ -1,477 +0,0 @@
1
- import {StatelessWebexPlugin} from '@webex/webex-core';
2
- import {isFunction} from 'lodash';
3
- import {uuid} from 'uuid';
4
-
5
- import {
6
- MEETINGS,
7
- STATS,
8
- MQA_STATS
9
- } from '../constants';
10
- import StatsHistory from '../stats/history';
11
- import StatsStream from '../stats/stream';
12
- import StatsFilter from '../stats/filter';
13
- import StatsEvents from '../stats/events';
14
- import StatsError from '../common/errors/stats';
15
-
16
- /**
17
- * @class MeetingStats
18
- */
19
- export default class MeetingStats extends StatelessWebexPlugin {
20
- namespace = MEETINGS;
21
-
22
- /**
23
- * @param {Object} attrs
24
- * @param {Object} options
25
- * @param {Object} [optionalCreateOptions]
26
- * @param {Boolean} optionalCreateOptions.history
27
- * @param {Boolean} optionalCreateOptions.mqa
28
- * @param {RTCRtpSender|RTCRtpReceiver} optionalCreateOptions.stream
29
- * @param {RTCRtpSender|RTCRtpReceiver} optionalCreateOptions.filter
30
- * @param {RTCPeerConnection} optionalCreateOptions.media
31
- * @param {String} optionalCreateOptions.id
32
- * @param {Function} optionalCreateOptions.onClose
33
- * @param {Function} optionalCreateOptions.onEvent
34
- * @param {Function} optionalCreateOptions.onData
35
- * if using filter or stream, media must also exist
36
- */
37
- constructor(attrs, options, optionalCreateOptions) {
38
- super({}, options);
39
- this.attrs = attrs;
40
- this.options = options;
41
- // what this stats object is configured to work with
42
- /**
43
- * @instance
44
- * @type {RTCPeerConnection}
45
- * @private
46
- * @memberof MeetingStats
47
- */
48
- this.peerConnection = null;
49
- /**
50
- * @instance
51
- * @type {RTCRtpSender|RTCRtpReceiver}
52
- * @private
53
- * @memberof MeetingStats
54
- */
55
- this.RTCRtpDirection = null;
56
- // usable values
57
- /**
58
- * @instance
59
- * @type {StatsHistory}
60
- * @readonly
61
- * @private
62
- * @memberof MeetingStats
63
- */
64
- this.history = null;
65
- /**
66
- * @instance
67
- * @type {StatsHistory}
68
- * @readonly
69
- * @private
70
- * @memberof MeetingStats
71
- */
72
- this.mqa = null;
73
- /**
74
- * @instance
75
- * @type {ReadableStream}
76
- * @readonly
77
- * @private
78
- * @memberof MeetingStats
79
- */
80
- this.stream = null;
81
- /**
82
- * @instance
83
- * @type {TransformStream}
84
- * @readonly
85
- * @private
86
- * @memberof MeetingStats
87
- */
88
- this.filter = null;
89
- /**
90
- * @instance
91
- * @type {StatsEvents}
92
- * @readonly
93
- * @private
94
- * @memberof MeetingStats
95
- */
96
- this.events = null;
97
- /**
98
- * @instance
99
- * @type {String}
100
- * @readonly
101
- * @private
102
- * @memberof MeetingStats
103
- */
104
- this.id = null;
105
- this.populate(optionalCreateOptions);
106
- }
107
-
108
- /**
109
- * @param {Object} [optionalCreateOptions]
110
- * @returns {undefined}
111
- * @private
112
- * @memberof MeetingStats
113
- */
114
- populate(optionalCreateOptions) {
115
- if (optionalCreateOptions) {
116
- if (optionalCreateOptions.history) {
117
- this.withHistory();
118
- }
119
- if (optionalCreateOptions.mqa) {
120
- this.withMQA();
121
- }
122
- if (optionalCreateOptions.filter && !optionalCreateOptions.stream && optionalCreateOptions.media) {
123
- this.withFilter(optionalCreateOptions.filter, optionalCreateOptions.media);
124
- }
125
- if (optionalCreateOptions.stream && !optionalCreateOptions.filter && optionalCreateOptions.media) {
126
- this.withStream(optionalCreateOptions.stream, optionalCreateOptions.media);
127
- }
128
- if (optionalCreateOptions.id) {
129
- this.withId(optionalCreateOptions.id);
130
- }
131
- if (optionalCreateOptions.onClose) {
132
- if (!isFunction(optionalCreateOptions.onClose)) {
133
- throw new TypeError('stats->populate#onClose must be a callback function for filtered data.');
134
- }
135
- this.onClose(optionalCreateOptions.onClose);
136
- }
137
- if (optionalCreateOptions.onEvent) {
138
- if (!isFunction(optionalCreateOptions.onEvent)) {
139
- throw new TypeError('stats->populate#onEvent must be a callback function for filtered data.');
140
- }
141
- if (this.history) {
142
- this.withEventsHistory(this.history, optionalCreateOptions.onEvent);
143
- }
144
- else {
145
- this.withEvents(optionalCreateOptions.onEvent);
146
- }
147
- }
148
- if (optionalCreateOptions.onData) {
149
- if (!isFunction(optionalCreateOptions.onData)) {
150
- throw new TypeError('stats->populate#onData must be a callback function for filtered data.');
151
- }
152
- this.onData(optionalCreateOptions.onData);
153
- }
154
- }
155
-
156
- return this;
157
- }
158
-
159
- /**
160
- * @param {WebRTCData} data
161
- * @returns {undefined}
162
- * @public
163
- * @memberof MeetingStats
164
- */
165
- doHistory(data) {
166
- if (this.history) {
167
- this.history.add(data);
168
- }
169
- }
170
-
171
- /**
172
- * @param {WebRTCData} data
173
- * @returns {undefined}
174
- * @public
175
- * @memberof MeetingStats
176
- */
177
- doMQA(data) {
178
- if (this.mqa && data.data) {
179
- if (!data.data.isEmpty()) {
180
- this.mqa.add(data.data.omit());
181
- }
182
- }
183
- }
184
-
185
- /**
186
- * @param {WebRTCData} data
187
- * @returns {undefined}
188
- * @public
189
- * @memberof MeetingStats
190
- */
191
- doEvents(data) {
192
- if (this.events) {
193
- this.events.event(data);
194
- }
195
- }
196
-
197
- /**
198
- * does all the work for the built properties
199
- * calls back a function with data from piped stream filter
200
- * @param {Function} cbFn
201
- * @returns {undefined}
202
- * @throws {Error} if the filter stream does not exist
203
- * @private
204
- * @memberof MeetingStats
205
- */
206
- onData(cbFn) {
207
- if (!this.filter) {
208
- throw new TypeError('The stats sender/receiver filter must be set up before data can be processed.');
209
- }
210
- this.filter.on(STATS.DATA, (filtered) => {
211
- this.doHistory(filtered);
212
- this.doMQA(filtered);
213
- this.doEvents(filtered);
214
- cbFn(filtered);
215
- });
216
-
217
- return this;
218
- }
219
-
220
- /**
221
- * triggered if the data stream closes
222
- * calls back a function with error
223
- * @param {Function} cbFn
224
- * @returns {undefined}
225
- * @private
226
- * @memberof MeetingStats
227
- */
228
- onClose(cbFn) {
229
- if (!this.filter) {
230
- throw new TypeError('the stats sender/receiver filter must be set up before data can be closed.');
231
- }
232
- this.stream.on(STATS.END, (err) => {
233
- if (!err) {
234
- err = new StatsError(`The stats stream for id: ${this.id} ended.`);
235
- }
236
- cbFn(err);
237
- });
238
-
239
- return this;
240
- }
241
-
242
- /**
243
- * constructs an event object on this instance
244
- * @param {StatsHistory} history
245
- * @param {Function} cb
246
- * @returns {MeetingStats}
247
- * @public
248
- * @memberof MeetingStats
249
- */
250
- withEventsHistory(history, cb) {
251
- const events = new StatsEvents(history, cb);
252
-
253
- this.setEvents(events);
254
-
255
- return this;
256
- }
257
-
258
- /**
259
- * constructs an event object on this instance
260
- * @param {Function} cb
261
- * @returns {MeetingStats}
262
- * @public
263
- * @memberof MeetingStats
264
- */
265
- withEvents(cb) {
266
- const events = new StatsEvents(null, cb);
267
-
268
- this.setEvents(events);
269
-
270
- return this;
271
- }
272
-
273
- /**
274
- * constructs a history object on this instance
275
- * @returns {MeetingStats}
276
- * @public
277
- * @memberof MeetingStats
278
- */
279
- withHistory() {
280
- const history = new StatsHistory(this.config.stats.historyMax);
281
-
282
- this.setHistory(history);
283
-
284
- return this;
285
- }
286
-
287
- /**
288
- * constructs a history object on this instance
289
- * @returns {MeetingStats}
290
- * @public
291
- * @memberof MeetingStats
292
- */
293
- withMQA() {
294
- const mqa = new StatsHistory(MQA_STATS.MQA_SIZE);
295
-
296
- this.setMQA(mqa);
297
-
298
- return this;
299
- }
300
-
301
- /**
302
- * constructs a readable stream object on this instance
303
- * @param {RTCRtpReceiver|RTCRtpSender} transceiverDirection
304
- * @param {RTCPeerConnection} peerConnection
305
- * @returns {MeetingStats}
306
- * @public
307
- * @memberof MeetingStats
308
- */
309
- withStream(transceiverDirection, peerConnection) {
310
- const stream = new StatsStream({
311
- rTCRtpDirection: transceiverDirection,
312
- peerConnection,
313
- interval: this.config.stats.interval
314
- });
315
-
316
- this.setStream(stream);
317
-
318
- return this;
319
- }
320
-
321
- /**
322
- * @param {RTCRtpReceiver|RTCRtpSender} transceiverDirection
323
- * @param {RTCPeerConnection} peerConnection
324
- * @returns {MeetingStats}
325
- * @public
326
- * @memberof MeetingStats
327
- */
328
- withFilter(transceiverDirection, peerConnection) {
329
- this.withStream(transceiverDirection, peerConnection);
330
- this.setFilter(new StatsFilter());
331
- this.getStream().pipe(this.getFilter());
332
-
333
- return this;
334
- }
335
-
336
- /**
337
- * constructs an id to match this stats object
338
- * takes params as precedence
339
- * @param {String} id
340
- * @returns {MeetingStats}
341
- * @public
342
- * @memberof MeetingStats
343
- */
344
- withId(id) {
345
- if (id) {
346
- this.setId(id);
347
- }
348
- else {
349
- this.setId(uuid.v4());
350
- }
351
-
352
- return this;
353
- }
354
-
355
- /**
356
- * @returns {MeetingStats}
357
- * @public
358
- * @memberof MeetingStats
359
- */
360
- build() {
361
- return this;
362
- }
363
-
364
- /**
365
- * @param {String} id
366
- * @returns {undefined}
367
- * @public
368
- * @memberof MeetingStats
369
- */
370
- setId(id) {
371
- this.id = id;
372
- }
373
-
374
- /**
375
- * @param {StatsHistory} history
376
- * @returns {undefined}
377
- * @public
378
- * @memberof MeetingStats
379
- */
380
- setHistory(history) {
381
- this.history = history;
382
- }
383
-
384
- /**
385
- * @param {StatsHistory} mqa
386
- * @returns {undefined}
387
- * @public
388
- * @memberof MeetingStats
389
- */
390
- setMQA(mqa) {
391
- this.mqa = mqa;
392
- }
393
-
394
- /**
395
- * @param {StatsEvent} events
396
- * @returns {undefined}
397
- * @public
398
- * @memberof MeetingStats
399
- */
400
- setEvents(events) {
401
- this.events = events;
402
- }
403
-
404
- /**
405
- * @param {Readable} stream
406
- * @returns {undefined}
407
- * @public
408
- * @memberof MeetingStats
409
- */
410
- setStream(stream) {
411
- this.stream = stream;
412
- }
413
-
414
- /**
415
- * @param {Transform} filter
416
- * @returns {undefined}
417
- * @public
418
- * @memberof MeetingStats
419
- */
420
- setFilter(filter) {
421
- this.filter = filter;
422
- }
423
-
424
- /**
425
- * @returns {String}
426
- * @public
427
- * @memberof MeetingStats
428
- */
429
- getId() {
430
- return this.id;
431
- }
432
-
433
- /**
434
- * @returns {StatsHistory}
435
- * @public
436
- * @memberof MeetingStats
437
- */
438
- getHistory() {
439
- return this.history;
440
- }
441
-
442
- /**
443
- * @returns {StatsHistory}
444
- * @public
445
- * @memberof MeetingStats
446
- */
447
- getMQA() {
448
- return this.mqa;
449
- }
450
-
451
- /**
452
- * @returns {StatsEvents}
453
- * @public
454
- * @memberof MeetingStats
455
- */
456
- getEvents() {
457
- return this.events;
458
- }
459
-
460
- /**
461
- * @returns {Readable}
462
- * @public
463
- * @memberof MeetingStats
464
- */
465
- getStream() {
466
- return this.stream;
467
- }
468
-
469
- /**
470
- * @returns {Transform}
471
- * @public
472
- * @memberof MeetingStats
473
- */
474
- getFilter() {
475
- return this.filter;
476
- }
477
- }
@@ -1,108 +0,0 @@
1
- import {EventEmitter} from 'events';
2
-
3
- import {Readable} from 'readable-stream';
4
- import {safeSetTimeout} from '@webex/common-timers';
5
-
6
- import {
7
- ERROR,
8
- STATS
9
- } from '../constants';
10
-
11
- const pcsByRTCRtpDirection = new WeakMap();
12
- const emittersByRTCRtpDirection = new WeakMap();
13
- const RTCRtpDirectionByEmitter = new WeakMap();
14
- const emittersByStream = new WeakMap();
15
- const timersByEmitter = new WeakMap();
16
-
17
- /**
18
- * Helper function that ensures no matter how many stats streams we create, we
19
- * don't poll the PeerConnection more than once per interval.
20
- * @param {EventEmitter} emitter
21
- * @param {Number} interval
22
- * @private
23
- * @returns {undefined}
24
- */
25
- const schedule = (emitter, interval) => {
26
- const timer = safeSetTimeout(() => {
27
- const direction = RTCRtpDirectionByEmitter.get(emitter);
28
- const pc = pcsByRTCRtpDirection.get(direction);
29
-
30
- if (direction) {
31
- direction.getStats()
32
- .then((stats) => {
33
- emitter.emit(STATS.DATA, stats);
34
- // TODO: Remove on 1.0 spec adoption
35
- // "closed" is supposed to be part of the {@link RTCPeerConnectionState}
36
- // enum according to spec, but at time of writing, was still implemented
37
- // in the {@link RTCSignalingState} enum.
38
- if (!(pc.signalingState === STATS.CLOSED || pc.connectionState === STATS.CLOSED)) {
39
- schedule(emitter, interval);
40
- }
41
- })
42
- .catch((err) => {
43
- emitter.emit(ERROR, err);
44
- });
45
- }
46
- }, interval);
47
-
48
- timersByEmitter.set(emitter, timer);
49
- };
50
-
51
- /**
52
- * Polls an {@link RTCPeerConnection} once per second and emits its {@link RTCStatsReport}
53
- * {@link RTCStatsReport}
54
- */
55
- export default class StatsStream extends Readable {
56
- /**
57
- * @private
58
- * @param {Object} config
59
- * @param {RTCRtpSender|RTCRtpReceiver} config.rTCRtpDirection
60
- * @param {RTCPeerConnection} config.peerConnection
61
- * @param {Number} config.interval
62
- */
63
- constructor(config = {}) {
64
- super({objectMode: true});
65
-
66
- this.interval = config.interval;
67
-
68
- if (!emittersByRTCRtpDirection.has(config.rTCRtpDirection)) {
69
- emittersByRTCRtpDirection.set(config.rTCRtpDirection, new EventEmitter());
70
- }
71
- const emitter = emittersByRTCRtpDirection.get(config.rTCRtpDirection);
72
-
73
- if (!emittersByStream.has(this)) {
74
- emittersByStream.set(this, emitter);
75
- }
76
- if (!RTCRtpDirectionByEmitter.has(emitter)) {
77
- RTCRtpDirectionByEmitter.set(emitter, config.rTCRtpDirection);
78
- }
79
-
80
- if (!pcsByRTCRtpDirection.has(config.rTCRtpDirection)) {
81
- pcsByRTCRtpDirection.set(config.rTCRtpDirection, config.peerConnection);
82
- }
83
-
84
- emitter.once(ERROR, (err) => {
85
- this.emit(ERROR, err);
86
- });
87
- }
88
-
89
- /**
90
- * See NodeJS Docs
91
- * @private
92
- * @returns {undefined}
93
- */
94
- _read() {
95
- const emitter = emittersByStream.get(this);
96
-
97
- emitter.once(STATS.DATA, (data) => {
98
- if (!this.isPaused()) {
99
- this.push(data);
100
- }
101
- });
102
-
103
- if (!timersByEmitter.has(emitter)) {
104
- schedule(emitter, this.interval);
105
- }
106
- }
107
- }
108
-
@@ -1,109 +0,0 @@
1
- import {keys, find, forEach} from 'lodash';
2
-
3
- import {
4
- DEFAULT_TRANSFORM_REGEX, DEFAULT_FF_TRANSFORM_REGEX, DEFAULT_GET_STATS_FILTER
5
- } from '../constants';
6
- import BrowserDetection from '../common/browser-detection';
7
-
8
- const {isBrowser} = BrowserDetection();
9
-
10
- const StatsTransformer = {
11
- isFF: isBrowser('firefox')
12
- };
13
-
14
- // convert the ids from the parsed stats objects into understandable keys
15
- StatsTransformer.simplify = (stat) => find(keys(StatsTransformer.isFF ? DEFAULT_FF_TRANSFORM_REGEX : DEFAULT_TRANSFORM_REGEX), (key) => {
16
- let value;
17
-
18
- if (StatsTransformer.isFF) {
19
- // FF stats are formatted poorly so we have to take what we can get
20
- value = (DEFAULT_FF_TRANSFORM_REGEX[key].regex.test(`${stat.type}${stat.kind ? `_${stat.kind}` : ''}_${stat.id}`) ? key : false);
21
- }
22
- else {
23
- // no other parameters necessary besides id
24
- value = (DEFAULT_TRANSFORM_REGEX[key].regex.test(stat.id) ? key : false);
25
- }
26
-
27
- if (value) {
28
- // others were included but have a value that exclude them from the list because of duplicates that weren't chosen
29
- if (DEFAULT_TRANSFORM_REGEX[value].decider) {
30
- if (stat[DEFAULT_TRANSFORM_REGEX[value].decider] !== DEFAULT_TRANSFORM_REGEX[value].selector) {
31
- value = false;
32
- }
33
-
34
- return value;
35
- }
36
- // // some types need to check against a type field as well
37
- if (DEFAULT_TRANSFORM_REGEX[value].profiler) {
38
- const mapStatToReadableType = stat[DEFAULT_TRANSFORM_REGEX[value].profiler.type];
39
-
40
- if (!((mapStatToReadableType && mapStatToReadableType.includes(DEFAULT_TRANSFORM_REGEX[value].profiler.value)) &&
41
- (value.toLowerCase().includes(DEFAULT_TRANSFORM_REGEX[value].profiler.value)))) {
42
- value = false;
43
-
44
- return value;
45
- }
46
- }
47
- }
48
-
49
- return value;
50
- });
51
-
52
- // parse the RTCStatsReport, extracting only the data we care about
53
- StatsTransformer.parse = (report) => {
54
- const target = {};
55
-
56
- // from the stats object
57
- // get the rtc stats report
58
- if (report && report.forEach && report.size > 0) {
59
- report.forEach((stat) => {
60
- // each report has internal data
61
- if (stat && stat.type) {
62
- // pull only certain types
63
- if (DEFAULT_GET_STATS_FILTER.types.includes(stat.type)) {
64
- // insert into the target the statistic mapped to it's statical id
65
- target[stat.id] = stat;
66
- }
67
- }
68
- });
69
- }
70
-
71
- return target;
72
- };
73
-
74
- StatsTransformer.convert = (parsed, options) => {
75
- // supply your own simplification function (rather than id as the key)
76
- if (options.simplifyFn) {
77
- return options.simplifyFn(parsed);
78
- }
79
- // else use ours that stores things like rtcOutAudio, rtpInVideo, etc
80
- const target = {};
81
-
82
- forEach(parsed, (stat) => {
83
- const key = StatsTransformer.simplify(stat);
84
-
85
- if (key) {
86
- target[key] = stat;
87
- }
88
- });
89
-
90
- return target;
91
- };
92
-
93
- // transform the RTCStatsReport into a much more readable, processable format
94
- StatsTransformer.transform = (report, options = {simplifyFn: undefined}) => {
95
- let data;
96
- // first parse it and cut out not necessary data
97
- const parsed = StatsTransformer.parse(report);
98
-
99
- // either convert the data or store each stat with it's id mapped as the key to the stat object itself
100
- if (DEFAULT_TRANSFORM_REGEX) {
101
- data = StatsTransformer.convert(parsed, options);
102
- }
103
- else {
104
- data = parsed;
105
- }
106
-
107
- return data;
108
- };
109
- export default StatsTransformer;