@webex/internal-plugin-metrics 3.5.0-next.4 → 3.5.0-next.5
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/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/metrics.js +1 -1
- package/dist/rtcMetrics/constants.js +11 -0
- package/dist/rtcMetrics/constants.js.map +1 -0
- package/dist/rtcMetrics/index.js +202 -0
- package/dist/rtcMetrics/index.js.map +1 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/rtcMetrics/constants.d.ts +4 -0
- package/dist/types/rtcMetrics/index.d.ts +71 -0
- package/package.json +11 -11
- package/src/index.ts +2 -0
- package/src/rtcMetrics/constants.ts +3 -0
- package/src/rtcMetrics/index.ts +186 -0
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +8 -3
- package/test/unit/spec/rtcMetrics/index.ts +155 -0
package/dist/index.js
CHANGED
|
@@ -46,6 +46,12 @@ _Object$defineProperty(exports, "OperationalMetrics", {
|
|
|
46
46
|
return _operationalMetrics.default;
|
|
47
47
|
}
|
|
48
48
|
});
|
|
49
|
+
_Object$defineProperty(exports, "RtcMetrics", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
get: function get() {
|
|
52
|
+
return _rtcMetrics.default;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
49
55
|
exports.Utils = void 0;
|
|
50
56
|
_Object$defineProperty(exports, "config", {
|
|
51
57
|
enumerable: true,
|
|
@@ -80,6 +86,7 @@ var _callDiagnosticMetricsLatencies = _interopRequireDefault(require("./call-dia
|
|
|
80
86
|
var _behavioralMetrics = _interopRequireDefault(require("./behavioral-metrics"));
|
|
81
87
|
var _operationalMetrics = _interopRequireDefault(require("./operational-metrics"));
|
|
82
88
|
var _businessMetrics = _interopRequireDefault(require("./business-metrics"));
|
|
89
|
+
var _rtcMetrics = _interopRequireDefault(require("./rtcMetrics"));
|
|
83
90
|
function _getRequireWildcardCache(e) { if ("function" != typeof _WeakMap) return null; var r = new _WeakMap(), t = new _WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
|
|
84
91
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = _Object$defineProperty && _Object$getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? _Object$getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? _Object$defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
85
92
|
/*!
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_webexCore","require","_metrics","_interopRequireWildcard","_config","_interopRequireDefault","_newMetrics","Utils","exports","CALL_DIAGNOSTIC_CONFIG","CallDiagnosticUtils","_callDiagnosticMetrics2","_callDiagnosticMetricsLatencies","_behavioralMetrics","_operationalMetrics","_businessMetrics","_getRequireWildcardCache","e","_WeakMap","r","t","__esModule","_typeof","default","has","get","n","__proto__","a","_Object$defineProperty","_Object$getOwnPropertyDescriptor","u","Object","prototype","hasOwnProperty","call","i","set","registerInternalPlugin","Metrics","config","NewMetrics"],"sources":["index.ts"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {registerInternalPlugin} from '@webex/webex-core';\n\nimport Metrics from './metrics';\nimport config from './config';\nimport NewMetrics from './new-metrics';\nimport * as Utils from './utils';\nimport {\n ClientEvent,\n ClientEventLeaveReason,\n SubmitBehavioralEvent,\n SubmitClientEvent,\n SubmitInternalEvent,\n SubmitOperationalEvent,\n SubmitMQE,\n PreComputedLatencies,\n} from './metrics.types';\nimport * as CALL_DIAGNOSTIC_CONFIG from './call-diagnostic/config';\nimport * as CallDiagnosticUtils from './call-diagnostic/call-diagnostic-metrics.util';\nimport CallDiagnosticMetrics from './call-diagnostic/call-diagnostic-metrics';\nimport CallDiagnosticLatencies from './call-diagnostic/call-diagnostic-metrics-latencies';\nimport BehavioralMetrics from './behavioral-metrics';\nimport OperationalMetrics from './operational-metrics';\nimport BusinessMetrics from './business-metrics';\n\nregisterInternalPlugin('metrics', Metrics, {\n config,\n});\n\nregisterInternalPlugin('newMetrics', NewMetrics, {\n config,\n});\n\nexport {default, getOSNameInternal} from './metrics';\n\nexport {\n config,\n CALL_DIAGNOSTIC_CONFIG,\n NewMetrics,\n Utils,\n CallDiagnosticUtils,\n CallDiagnosticLatencies,\n CallDiagnosticMetrics,\n BehavioralMetrics,\n OperationalMetrics,\n BusinessMetrics,\n};\nexport type {\n ClientEvent,\n ClientEventLeaveReason,\n SubmitBehavioralEvent,\n SubmitClientEvent,\n SubmitInternalEvent,\n SubmitMQE,\n SubmitOperationalEvent,\n PreComputedLatencies,\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"names":["_webexCore","require","_metrics","_interopRequireWildcard","_config","_interopRequireDefault","_newMetrics","Utils","exports","CALL_DIAGNOSTIC_CONFIG","CallDiagnosticUtils","_callDiagnosticMetrics2","_callDiagnosticMetricsLatencies","_behavioralMetrics","_operationalMetrics","_businessMetrics","_rtcMetrics","_getRequireWildcardCache","e","_WeakMap","r","t","__esModule","_typeof","default","has","get","n","__proto__","a","_Object$defineProperty","_Object$getOwnPropertyDescriptor","u","Object","prototype","hasOwnProperty","call","i","set","registerInternalPlugin","Metrics","config","NewMetrics"],"sources":["index.ts"],"sourcesContent":["/*!\n * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.\n */\n\nimport {registerInternalPlugin} from '@webex/webex-core';\n\nimport Metrics from './metrics';\nimport config from './config';\nimport NewMetrics from './new-metrics';\nimport * as Utils from './utils';\nimport {\n ClientEvent,\n ClientEventLeaveReason,\n SubmitBehavioralEvent,\n SubmitClientEvent,\n SubmitInternalEvent,\n SubmitOperationalEvent,\n SubmitMQE,\n PreComputedLatencies,\n} from './metrics.types';\nimport * as CALL_DIAGNOSTIC_CONFIG from './call-diagnostic/config';\nimport * as CallDiagnosticUtils from './call-diagnostic/call-diagnostic-metrics.util';\nimport CallDiagnosticMetrics from './call-diagnostic/call-diagnostic-metrics';\nimport CallDiagnosticLatencies from './call-diagnostic/call-diagnostic-metrics-latencies';\nimport BehavioralMetrics from './behavioral-metrics';\nimport OperationalMetrics from './operational-metrics';\nimport BusinessMetrics from './business-metrics';\nimport RtcMetrics from './rtcMetrics';\n\nregisterInternalPlugin('metrics', Metrics, {\n config,\n});\n\nregisterInternalPlugin('newMetrics', NewMetrics, {\n config,\n});\n\nexport {default, getOSNameInternal} from './metrics';\n\nexport {\n config,\n CALL_DIAGNOSTIC_CONFIG,\n NewMetrics,\n Utils,\n CallDiagnosticUtils,\n CallDiagnosticLatencies,\n CallDiagnosticMetrics,\n BehavioralMetrics,\n OperationalMetrics,\n BusinessMetrics,\n RtcMetrics,\n};\nexport type {\n ClientEvent,\n ClientEventLeaveReason,\n SubmitBehavioralEvent,\n SubmitClientEvent,\n SubmitInternalEvent,\n SubmitMQE,\n SubmitOperationalEvent,\n PreComputedLatencies,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,IAAAA,UAAA,GAAAC,OAAA;AAEA,IAAAC,QAAA,GAAAC,uBAAA,CAAAF,OAAA;AACA,IAAAG,OAAA,GAAAC,sBAAA,CAAAJ,OAAA;AACA,IAAAK,WAAA,GAAAD,sBAAA,CAAAJ,OAAA;AACA,IAAAM,KAAA,GAAAJ,uBAAA,CAAAF,OAAA;AAAiCO,OAAA,CAAAD,KAAA,GAAAA,KAAA;AAWjC,IAAAE,sBAAA,GAAAN,uBAAA,CAAAF,OAAA;AAAmEO,OAAA,CAAAC,sBAAA,GAAAA,sBAAA;AACnE,IAAAC,mBAAA,GAAAP,uBAAA,CAAAF,OAAA;AAAsFO,OAAA,CAAAE,mBAAA,GAAAA,mBAAA;AACtF,IAAAC,uBAAA,GAAAN,sBAAA,CAAAJ,OAAA;AACA,IAAAW,+BAAA,GAAAP,sBAAA,CAAAJ,OAAA;AACA,IAAAY,kBAAA,GAAAR,sBAAA,CAAAJ,OAAA;AACA,IAAAa,mBAAA,GAAAT,sBAAA,CAAAJ,OAAA;AACA,IAAAc,gBAAA,GAAAV,sBAAA,CAAAJ,OAAA;AACA,IAAAe,WAAA,GAAAX,sBAAA,CAAAJ,OAAA;AAAsC,SAAAgB,yBAAAC,CAAA,6BAAAC,QAAA,mBAAAC,CAAA,OAAAD,QAAA,IAAAE,CAAA,OAAAF,QAAA,YAAAF,wBAAA,YAAAA,yBAAAC,CAAA,WAAAA,CAAA,GAAAG,CAAA,GAAAD,CAAA,KAAAF,CAAA;AAAA,SAAAf,wBAAAe,CAAA,EAAAE,CAAA,SAAAA,CAAA,IAAAF,CAAA,IAAAA,CAAA,CAAAI,UAAA,SAAAJ,CAAA,eAAAA,CAAA,gBAAAK,OAAA,CAAAL,CAAA,0BAAAA,CAAA,WAAAM,OAAA,EAAAN,CAAA,QAAAG,CAAA,GAAAJ,wBAAA,CAAAG,CAAA,OAAAC,CAAA,IAAAA,CAAA,CAAAI,GAAA,CAAAP,CAAA,UAAAG,CAAA,CAAAK,GAAA,CAAAR,CAAA,OAAAS,CAAA,KAAAC,SAAA,UAAAC,CAAA,GAAAC,sBAAA,IAAAC,gCAAA,WAAAC,CAAA,IAAAd,CAAA,oBAAAc,CAAA,IAAAC,MAAA,CAAAC,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAlB,CAAA,EAAAc,CAAA,SAAAK,CAAA,GAAAR,CAAA,GAAAE,gCAAA,CAAAb,CAAA,EAAAc,CAAA,UAAAK,CAAA,KAAAA,CAAA,CAAAX,GAAA,IAAAW,CAAA,CAAAC,GAAA,IAAAR,sBAAA,CAAAH,CAAA,EAAAK,CAAA,EAAAK,CAAA,IAAAV,CAAA,CAAAK,CAAA,IAAAd,CAAA,CAAAc,CAAA,YAAAL,CAAA,CAAAH,OAAA,GAAAN,CAAA,EAAAG,CAAA,IAAAA,CAAA,CAAAiB,GAAA,CAAApB,CAAA,EAAAS,CAAA,GAAAA,CAAA;AA3BtC;AACA;AACA;;AA2BA,IAAAY,iCAAsB,EAAC,SAAS,EAAEC,gBAAO,EAAE;EACzCC,MAAM,EAANA;AACF,CAAC,CAAC;AAEF,IAAAF,iCAAsB,EAAC,YAAY,EAAEG,mBAAU,EAAE;EAC/CD,MAAM,EAANA;AACF,CAAC,CAAC"}
|
package/dist/metrics.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
|
|
4
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.default = void 0;
|
|
8
|
+
var RTC_METRICS = exports.default = {
|
|
9
|
+
APP_ID: 'FFB51ED5-4319-4C55-8303-B1F2FCCDE231'
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["RTC_METRICS","exports","default","APP_ID"],"sources":["constants.ts"],"sourcesContent":["const RTC_METRICS = {APP_ID: 'FFB51ED5-4319-4C55-8303-B1F2FCCDE231'};\n\nexport {RTC_METRICS as default};\n"],"mappings":";;;;;;;AAAA,IAAMA,WAAW,GAAAC,OAAA,CAAAC,OAAA,GAAG;EAACC,MAAM,EAAE;AAAsC,CAAC"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _typeof = require("@babel/runtime-corejs2/helpers/typeof");
|
|
4
|
+
var _WeakMap = require("@babel/runtime-corejs2/core-js/weak-map");
|
|
5
|
+
var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
|
|
6
|
+
var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor");
|
|
7
|
+
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
|
|
8
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
9
|
+
value: true
|
|
10
|
+
});
|
|
11
|
+
exports.default = void 0;
|
|
12
|
+
var _stringify = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/json/stringify"));
|
|
13
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
|
|
14
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
|
|
15
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/defineProperty"));
|
|
16
|
+
var _uuid = _interopRequireDefault(require("uuid"));
|
|
17
|
+
var CallDiagnosticUtils = _interopRequireWildcard(require("../call-diagnostic/call-diagnostic-metrics.util"));
|
|
18
|
+
var _constants = _interopRequireDefault(require("./constants"));
|
|
19
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof _WeakMap) return null; var r = new _WeakMap(), t = new _WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
|
|
20
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = _Object$defineProperty && _Object$getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? _Object$getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? _Object$defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
21
|
+
/* eslint-disable class-methods-use-this */
|
|
22
|
+
|
|
23
|
+
var parseJsonPayload = function parseJsonPayload(payload) {
|
|
24
|
+
try {
|
|
25
|
+
if (payload && payload[0]) {
|
|
26
|
+
return JSON.parse(payload[0]);
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
} catch (_) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Rtc Metrics
|
|
36
|
+
*/
|
|
37
|
+
var RtcMetrics = exports.default = /*#__PURE__*/function () {
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the interval.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} webex - The main `webex` object.
|
|
42
|
+
* @param {string} meetingId - The meeting id.
|
|
43
|
+
* @param {string} correlationId - The correlation id.
|
|
44
|
+
*/
|
|
45
|
+
function RtcMetrics(webex, meetingId, correlationId) {
|
|
46
|
+
(0, _classCallCheck2.default)(this, RtcMetrics);
|
|
47
|
+
/**
|
|
48
|
+
* Array of MetricData items to be sent to the metrics service.
|
|
49
|
+
*/
|
|
50
|
+
(0, _defineProperty2.default)(this, "metricsQueue", []);
|
|
51
|
+
(0, _defineProperty2.default)(this, "intervalId", void 0);
|
|
52
|
+
(0, _defineProperty2.default)(this, "webex", void 0);
|
|
53
|
+
(0, _defineProperty2.default)(this, "meetingId", void 0);
|
|
54
|
+
(0, _defineProperty2.default)(this, "correlationId", void 0);
|
|
55
|
+
(0, _defineProperty2.default)(this, "connectionId", void 0);
|
|
56
|
+
(0, _defineProperty2.default)(this, "shouldSendMetricsOnNextStatsReport", void 0);
|
|
57
|
+
// `window` is used to prevent typescript from returning a NodeJS.Timer.
|
|
58
|
+
this.intervalId = window.setInterval(this.sendMetricsInQueue.bind(this), 30 * 1000);
|
|
59
|
+
this.meetingId = meetingId;
|
|
60
|
+
this.webex = webex;
|
|
61
|
+
this.correlationId = correlationId;
|
|
62
|
+
this.resetConnection();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check to see if the metrics queue has any items.
|
|
67
|
+
*
|
|
68
|
+
* @returns {void}
|
|
69
|
+
*/
|
|
70
|
+
(0, _createClass2.default)(RtcMetrics, [{
|
|
71
|
+
key: "sendMetricsInQueue",
|
|
72
|
+
value: function sendMetricsInQueue() {
|
|
73
|
+
if (this.metricsQueue.length) {
|
|
74
|
+
this.sendMetrics();
|
|
75
|
+
this.metricsQueue = [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Forces sending metrics when we get the next stats-report
|
|
81
|
+
*
|
|
82
|
+
* This is useful for cases when something important happens that affects the media connection,
|
|
83
|
+
* for example when we move from lobby into the meeting.
|
|
84
|
+
*
|
|
85
|
+
* @returns {void}
|
|
86
|
+
*/
|
|
87
|
+
}, {
|
|
88
|
+
key: "sendNextMetrics",
|
|
89
|
+
value: function sendNextMetrics() {
|
|
90
|
+
this.shouldSendMetricsOnNextStatsReport = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Add metrics items to the metrics queue.
|
|
95
|
+
*
|
|
96
|
+
* @param {object} data - An object with a payload array of metrics items.
|
|
97
|
+
*
|
|
98
|
+
* @returns {void}
|
|
99
|
+
*/
|
|
100
|
+
}, {
|
|
101
|
+
key: "addMetrics",
|
|
102
|
+
value: function addMetrics(data) {
|
|
103
|
+
if (data.payload.length) {
|
|
104
|
+
if (data.name === 'stats-report') {
|
|
105
|
+
data.payload = data.payload.map(this.anonymizeIp);
|
|
106
|
+
}
|
|
107
|
+
this.metricsQueue.push(data);
|
|
108
|
+
if (this.shouldSendMetricsOnNextStatsReport && data.name === 'stats-report') {
|
|
109
|
+
// this is the first useful set of data (WCME gives it to us after 5s), send it out immediately
|
|
110
|
+
// in case the user is unhappy and closes the browser early
|
|
111
|
+
this.sendMetricsInQueue();
|
|
112
|
+
this.shouldSendMetricsOnNextStatsReport = false;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
// If a connection fails, send the rest of the metrics in queue and get a new connection id.
|
|
116
|
+
var parsedPayload = parseJsonPayload(data.payload);
|
|
117
|
+
if (data.name === 'onconnectionstatechange' && parsedPayload && parsedPayload.value === 'failed') {
|
|
118
|
+
this.sendMetricsInQueue();
|
|
119
|
+
this.resetConnection();
|
|
120
|
+
}
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error(e);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Clear the metrics interval.
|
|
129
|
+
*
|
|
130
|
+
* @returns {void}
|
|
131
|
+
*/
|
|
132
|
+
}, {
|
|
133
|
+
key: "closeMetrics",
|
|
134
|
+
value: function closeMetrics() {
|
|
135
|
+
this.sendMetricsInQueue();
|
|
136
|
+
clearInterval(this.intervalId);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Anonymize IP addresses.
|
|
141
|
+
*
|
|
142
|
+
* @param {array} stats - An RTCStatsReport organized into an array of strings.
|
|
143
|
+
* @returns {string}
|
|
144
|
+
*/
|
|
145
|
+
}, {
|
|
146
|
+
key: "anonymizeIp",
|
|
147
|
+
value: function anonymizeIp(stats) {
|
|
148
|
+
var data = JSON.parse(stats);
|
|
149
|
+
// on local and remote candidates, anonymize the last 4 bits.
|
|
150
|
+
if (data.type === 'local-candidate' || data.type === 'remote-candidate') {
|
|
151
|
+
data.ip = CallDiagnosticUtils.anonymizeIPAddress(data.ip) || undefined;
|
|
152
|
+
data.address = CallDiagnosticUtils.anonymizeIPAddress(data.address) || undefined;
|
|
153
|
+
data.relatedAddress = CallDiagnosticUtils.anonymizeIPAddress(data.relatedAddress) || undefined;
|
|
154
|
+
}
|
|
155
|
+
return (0, _stringify.default)(data);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Set a new connection id.
|
|
160
|
+
*
|
|
161
|
+
* @returns {void}
|
|
162
|
+
*/
|
|
163
|
+
}, {
|
|
164
|
+
key: "resetConnection",
|
|
165
|
+
value: function resetConnection() {
|
|
166
|
+
this.connectionId = _uuid.default.v4();
|
|
167
|
+
this.shouldSendMetricsOnNextStatsReport = true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Send metrics to the metrics service.
|
|
172
|
+
*
|
|
173
|
+
* @returns {void}
|
|
174
|
+
*/
|
|
175
|
+
}, {
|
|
176
|
+
key: "sendMetrics",
|
|
177
|
+
value: function sendMetrics() {
|
|
178
|
+
this.webex.request({
|
|
179
|
+
method: 'POST',
|
|
180
|
+
service: 'unifiedTelemetry',
|
|
181
|
+
resource: 'metric/v2',
|
|
182
|
+
headers: {
|
|
183
|
+
type: 'webrtcMedia',
|
|
184
|
+
appId: _constants.default.APP_ID
|
|
185
|
+
},
|
|
186
|
+
body: {
|
|
187
|
+
metrics: [{
|
|
188
|
+
type: 'webrtc',
|
|
189
|
+
version: '1.1.0',
|
|
190
|
+
userId: this.webex.internal.device.userId,
|
|
191
|
+
meetingId: this.meetingId,
|
|
192
|
+
correlationId: this.correlationId,
|
|
193
|
+
connectionId: this.connectionId,
|
|
194
|
+
data: this.metricsQueue
|
|
195
|
+
}]
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}]);
|
|
200
|
+
return RtcMetrics;
|
|
201
|
+
}();
|
|
202
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_uuid","_interopRequireDefault","require","CallDiagnosticUtils","_interopRequireWildcard","_constants","_getRequireWildcardCache","e","_WeakMap","r","t","__esModule","_typeof","default","has","get","n","__proto__","a","_Object$defineProperty","_Object$getOwnPropertyDescriptor","u","Object","prototype","hasOwnProperty","call","i","set","parseJsonPayload","payload","JSON","parse","_","RtcMetrics","exports","webex","meetingId","correlationId","_classCallCheck2","_defineProperty2","intervalId","window","setInterval","sendMetricsInQueue","bind","resetConnection","_createClass2","key","value","metricsQueue","length","sendMetrics","sendNextMetrics","shouldSendMetricsOnNextStatsReport","addMetrics","data","name","map","anonymizeIp","push","parsedPayload","console","error","closeMetrics","clearInterval","stats","type","ip","anonymizeIPAddress","undefined","address","relatedAddress","_stringify","connectionId","uuid","v4","request","method","service","resource","headers","appId","RTC_METRICS","APP_ID","body","metrics","version","userId","internal","device"],"sources":["index.ts"],"sourcesContent":["/* eslint-disable class-methods-use-this */\nimport uuid from 'uuid';\nimport * as CallDiagnosticUtils from '../call-diagnostic/call-diagnostic-metrics.util';\nimport RTC_METRICS from './constants';\n\nconst parseJsonPayload = (payload: any[]): any | null => {\n try {\n if (payload && payload[0]) {\n return JSON.parse(payload[0]);\n }\n\n return null;\n } catch (_) {\n return null;\n }\n};\n\n/**\n * Rtc Metrics\n */\nexport default class RtcMetrics {\n /**\n * Array of MetricData items to be sent to the metrics service.\n */\n metricsQueue = [];\n\n intervalId: number;\n\n webex: any;\n\n meetingId: string;\n\n correlationId: string;\n\n connectionId: string;\n\n shouldSendMetricsOnNextStatsReport: boolean;\n\n /**\n * Initialize the interval.\n *\n * @param {object} webex - The main `webex` object.\n * @param {string} meetingId - The meeting id.\n * @param {string} correlationId - The correlation id.\n */\n constructor(webex, meetingId, correlationId) {\n // `window` is used to prevent typescript from returning a NodeJS.Timer.\n this.intervalId = window.setInterval(this.sendMetricsInQueue.bind(this), 30 * 1000);\n this.meetingId = meetingId;\n this.webex = webex;\n this.correlationId = correlationId;\n this.resetConnection();\n }\n\n /**\n * Check to see if the metrics queue has any items.\n *\n * @returns {void}\n */\n public sendMetricsInQueue() {\n if (this.metricsQueue.length) {\n this.sendMetrics();\n this.metricsQueue = [];\n }\n }\n\n /**\n * Forces sending metrics when we get the next stats-report\n *\n * This is useful for cases when something important happens that affects the media connection,\n * for example when we move from lobby into the meeting.\n *\n * @returns {void}\n */\n public sendNextMetrics() {\n this.shouldSendMetricsOnNextStatsReport = true;\n }\n\n /**\n * Add metrics items to the metrics queue.\n *\n * @param {object} data - An object with a payload array of metrics items.\n *\n * @returns {void}\n */\n addMetrics(data) {\n if (data.payload.length) {\n if (data.name === 'stats-report') {\n data.payload = data.payload.map(this.anonymizeIp);\n }\n\n this.metricsQueue.push(data);\n\n if (this.shouldSendMetricsOnNextStatsReport && data.name === 'stats-report') {\n // this is the first useful set of data (WCME gives it to us after 5s), send it out immediately\n // in case the user is unhappy and closes the browser early\n this.sendMetricsInQueue();\n this.shouldSendMetricsOnNextStatsReport = false;\n }\n\n try {\n // If a connection fails, send the rest of the metrics in queue and get a new connection id.\n const parsedPayload = parseJsonPayload(data.payload);\n if (\n data.name === 'onconnectionstatechange' &&\n parsedPayload &&\n parsedPayload.value === 'failed'\n ) {\n this.sendMetricsInQueue();\n this.resetConnection();\n }\n } catch (e) {\n console.error(e);\n }\n }\n }\n\n /**\n * Clear the metrics interval.\n *\n * @returns {void}\n */\n closeMetrics() {\n this.sendMetricsInQueue();\n clearInterval(this.intervalId);\n }\n\n /**\n * Anonymize IP addresses.\n *\n * @param {array} stats - An RTCStatsReport organized into an array of strings.\n * @returns {string}\n */\n anonymizeIp(stats: string): string {\n const data = JSON.parse(stats);\n // on local and remote candidates, anonymize the last 4 bits.\n if (data.type === 'local-candidate' || data.type === 'remote-candidate') {\n data.ip = CallDiagnosticUtils.anonymizeIPAddress(data.ip) || undefined;\n data.address = CallDiagnosticUtils.anonymizeIPAddress(data.address) || undefined;\n data.relatedAddress =\n CallDiagnosticUtils.anonymizeIPAddress(data.relatedAddress) || undefined;\n }\n\n return JSON.stringify(data);\n }\n\n /**\n * Set a new connection id.\n *\n * @returns {void}\n */\n private resetConnection() {\n this.connectionId = uuid.v4();\n this.shouldSendMetricsOnNextStatsReport = true;\n }\n\n /**\n * Send metrics to the metrics service.\n *\n * @returns {void}\n */\n private sendMetrics() {\n this.webex.request({\n method: 'POST',\n service: 'unifiedTelemetry',\n resource: 'metric/v2',\n headers: {\n type: 'webrtcMedia',\n appId: RTC_METRICS.APP_ID,\n },\n body: {\n metrics: [\n {\n type: 'webrtc',\n version: '1.1.0',\n userId: this.webex.internal.device.userId,\n meetingId: this.meetingId,\n correlationId: this.correlationId,\n connectionId: this.connectionId,\n data: this.metricsQueue,\n },\n ],\n },\n });\n }\n}\n"],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import CallDiagnosticLatencies from './call-diagnostic/call-diagnostic-metrics-l
|
|
|
12
12
|
import BehavioralMetrics from './behavioral-metrics';
|
|
13
13
|
import OperationalMetrics from './operational-metrics';
|
|
14
14
|
import BusinessMetrics from './business-metrics';
|
|
15
|
+
import RtcMetrics from './rtcMetrics';
|
|
15
16
|
export { default, getOSNameInternal } from './metrics';
|
|
16
|
-
export { config, CALL_DIAGNOSTIC_CONFIG, NewMetrics, Utils, CallDiagnosticUtils, CallDiagnosticLatencies, CallDiagnosticMetrics, BehavioralMetrics, OperationalMetrics, BusinessMetrics, };
|
|
17
|
+
export { config, CALL_DIAGNOSTIC_CONFIG, NewMetrics, Utils, CallDiagnosticUtils, CallDiagnosticLatencies, CallDiagnosticMetrics, BehavioralMetrics, OperationalMetrics, BusinessMetrics, RtcMetrics, };
|
|
17
18
|
export type { ClientEvent, ClientEventLeaveReason, SubmitBehavioralEvent, SubmitClientEvent, SubmitInternalEvent, SubmitMQE, SubmitOperationalEvent, PreComputedLatencies, };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rtc Metrics
|
|
3
|
+
*/
|
|
4
|
+
export default class RtcMetrics {
|
|
5
|
+
/**
|
|
6
|
+
* Array of MetricData items to be sent to the metrics service.
|
|
7
|
+
*/
|
|
8
|
+
metricsQueue: any[];
|
|
9
|
+
intervalId: number;
|
|
10
|
+
webex: any;
|
|
11
|
+
meetingId: string;
|
|
12
|
+
correlationId: string;
|
|
13
|
+
connectionId: string;
|
|
14
|
+
shouldSendMetricsOnNextStatsReport: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the interval.
|
|
17
|
+
*
|
|
18
|
+
* @param {object} webex - The main `webex` object.
|
|
19
|
+
* @param {string} meetingId - The meeting id.
|
|
20
|
+
* @param {string} correlationId - The correlation id.
|
|
21
|
+
*/
|
|
22
|
+
constructor(webex: any, meetingId: any, correlationId: any);
|
|
23
|
+
/**
|
|
24
|
+
* Check to see if the metrics queue has any items.
|
|
25
|
+
*
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
sendMetricsInQueue(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Forces sending metrics when we get the next stats-report
|
|
31
|
+
*
|
|
32
|
+
* This is useful for cases when something important happens that affects the media connection,
|
|
33
|
+
* for example when we move from lobby into the meeting.
|
|
34
|
+
*
|
|
35
|
+
* @returns {void}
|
|
36
|
+
*/
|
|
37
|
+
sendNextMetrics(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Add metrics items to the metrics queue.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} data - An object with a payload array of metrics items.
|
|
42
|
+
*
|
|
43
|
+
* @returns {void}
|
|
44
|
+
*/
|
|
45
|
+
addMetrics(data: any): void;
|
|
46
|
+
/**
|
|
47
|
+
* Clear the metrics interval.
|
|
48
|
+
*
|
|
49
|
+
* @returns {void}
|
|
50
|
+
*/
|
|
51
|
+
closeMetrics(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Anonymize IP addresses.
|
|
54
|
+
*
|
|
55
|
+
* @param {array} stats - An RTCStatsReport organized into an array of strings.
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
anonymizeIp(stats: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Set a new connection id.
|
|
61
|
+
*
|
|
62
|
+
* @returns {void}
|
|
63
|
+
*/
|
|
64
|
+
private resetConnection;
|
|
65
|
+
/**
|
|
66
|
+
* Send metrics to the metrics service.
|
|
67
|
+
*
|
|
68
|
+
* @returns {void}
|
|
69
|
+
*/
|
|
70
|
+
private sendMetrics;
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -26,22 +26,22 @@
|
|
|
26
26
|
"@webex/eslint-config-legacy": "0.0.0",
|
|
27
27
|
"@webex/jest-config-legacy": "0.0.0",
|
|
28
28
|
"@webex/legacy-tools": "0.0.0",
|
|
29
|
-
"@webex/test-helper-chai": "3.5.0-next.
|
|
30
|
-
"@webex/test-helper-mocha": "3.5.0-next.
|
|
31
|
-
"@webex/test-helper-mock-webex": "3.5.0-next.
|
|
32
|
-
"@webex/test-helper-test-users": "3.5.0-next.
|
|
29
|
+
"@webex/test-helper-chai": "3.5.0-next.5",
|
|
30
|
+
"@webex/test-helper-mocha": "3.5.0-next.5",
|
|
31
|
+
"@webex/test-helper-mock-webex": "3.5.0-next.5",
|
|
32
|
+
"@webex/test-helper-test-users": "3.5.0-next.5",
|
|
33
33
|
"eslint": "^8.24.0",
|
|
34
34
|
"prettier": "^2.7.1",
|
|
35
35
|
"sinon": "^9.2.4"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@webex/common": "3.5.0-next.
|
|
39
|
-
"@webex/common-timers": "3.5.0-next.
|
|
38
|
+
"@webex/common": "3.5.0-next.5",
|
|
39
|
+
"@webex/common-timers": "3.5.0-next.5",
|
|
40
40
|
"@webex/event-dictionary-ts": "^1.0.1546",
|
|
41
|
-
"@webex/internal-plugin-metrics": "3.5.0-next.
|
|
42
|
-
"@webex/test-helper-chai": "3.5.0-next.
|
|
43
|
-
"@webex/test-helper-mock-webex": "3.5.0-next.
|
|
44
|
-
"@webex/webex-core": "3.5.0-next.
|
|
41
|
+
"@webex/internal-plugin-metrics": "3.5.0-next.5",
|
|
42
|
+
"@webex/test-helper-chai": "3.5.0-next.5",
|
|
43
|
+
"@webex/test-helper-mock-webex": "3.5.0-next.5",
|
|
44
|
+
"@webex/webex-core": "3.5.0-next.5",
|
|
45
45
|
"ip-anonymize": "^0.1.0",
|
|
46
46
|
"lodash": "^4.17.21",
|
|
47
47
|
"uuid": "^3.3.2"
|
|
@@ -54,5 +54,5 @@
|
|
|
54
54
|
"test:style": "eslint ./src/**/*.*",
|
|
55
55
|
"test:unit": "webex-legacy-tools test --unit --runner mocha"
|
|
56
56
|
},
|
|
57
|
-
"version": "3.5.0-next.
|
|
57
|
+
"version": "3.5.0-next.5"
|
|
58
58
|
}
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ import CallDiagnosticLatencies from './call-diagnostic/call-diagnostic-metrics-l
|
|
|
25
25
|
import BehavioralMetrics from './behavioral-metrics';
|
|
26
26
|
import OperationalMetrics from './operational-metrics';
|
|
27
27
|
import BusinessMetrics from './business-metrics';
|
|
28
|
+
import RtcMetrics from './rtcMetrics';
|
|
28
29
|
|
|
29
30
|
registerInternalPlugin('metrics', Metrics, {
|
|
30
31
|
config,
|
|
@@ -47,6 +48,7 @@ export {
|
|
|
47
48
|
BehavioralMetrics,
|
|
48
49
|
OperationalMetrics,
|
|
49
50
|
BusinessMetrics,
|
|
51
|
+
RtcMetrics,
|
|
50
52
|
};
|
|
51
53
|
export type {
|
|
52
54
|
ClientEvent,
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
import uuid from 'uuid';
|
|
3
|
+
import * as CallDiagnosticUtils from '../call-diagnostic/call-diagnostic-metrics.util';
|
|
4
|
+
import RTC_METRICS from './constants';
|
|
5
|
+
|
|
6
|
+
const parseJsonPayload = (payload: any[]): any | null => {
|
|
7
|
+
try {
|
|
8
|
+
if (payload && payload[0]) {
|
|
9
|
+
return JSON.parse(payload[0]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return null;
|
|
13
|
+
} catch (_) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Rtc Metrics
|
|
20
|
+
*/
|
|
21
|
+
export default class RtcMetrics {
|
|
22
|
+
/**
|
|
23
|
+
* Array of MetricData items to be sent to the metrics service.
|
|
24
|
+
*/
|
|
25
|
+
metricsQueue = [];
|
|
26
|
+
|
|
27
|
+
intervalId: number;
|
|
28
|
+
|
|
29
|
+
webex: any;
|
|
30
|
+
|
|
31
|
+
meetingId: string;
|
|
32
|
+
|
|
33
|
+
correlationId: string;
|
|
34
|
+
|
|
35
|
+
connectionId: string;
|
|
36
|
+
|
|
37
|
+
shouldSendMetricsOnNextStatsReport: boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Initialize the interval.
|
|
41
|
+
*
|
|
42
|
+
* @param {object} webex - The main `webex` object.
|
|
43
|
+
* @param {string} meetingId - The meeting id.
|
|
44
|
+
* @param {string} correlationId - The correlation id.
|
|
45
|
+
*/
|
|
46
|
+
constructor(webex, meetingId, correlationId) {
|
|
47
|
+
// `window` is used to prevent typescript from returning a NodeJS.Timer.
|
|
48
|
+
this.intervalId = window.setInterval(this.sendMetricsInQueue.bind(this), 30 * 1000);
|
|
49
|
+
this.meetingId = meetingId;
|
|
50
|
+
this.webex = webex;
|
|
51
|
+
this.correlationId = correlationId;
|
|
52
|
+
this.resetConnection();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check to see if the metrics queue has any items.
|
|
57
|
+
*
|
|
58
|
+
* @returns {void}
|
|
59
|
+
*/
|
|
60
|
+
public sendMetricsInQueue() {
|
|
61
|
+
if (this.metricsQueue.length) {
|
|
62
|
+
this.sendMetrics();
|
|
63
|
+
this.metricsQueue = [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Forces sending metrics when we get the next stats-report
|
|
69
|
+
*
|
|
70
|
+
* This is useful for cases when something important happens that affects the media connection,
|
|
71
|
+
* for example when we move from lobby into the meeting.
|
|
72
|
+
*
|
|
73
|
+
* @returns {void}
|
|
74
|
+
*/
|
|
75
|
+
public sendNextMetrics() {
|
|
76
|
+
this.shouldSendMetricsOnNextStatsReport = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Add metrics items to the metrics queue.
|
|
81
|
+
*
|
|
82
|
+
* @param {object} data - An object with a payload array of metrics items.
|
|
83
|
+
*
|
|
84
|
+
* @returns {void}
|
|
85
|
+
*/
|
|
86
|
+
addMetrics(data) {
|
|
87
|
+
if (data.payload.length) {
|
|
88
|
+
if (data.name === 'stats-report') {
|
|
89
|
+
data.payload = data.payload.map(this.anonymizeIp);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.metricsQueue.push(data);
|
|
93
|
+
|
|
94
|
+
if (this.shouldSendMetricsOnNextStatsReport && data.name === 'stats-report') {
|
|
95
|
+
// this is the first useful set of data (WCME gives it to us after 5s), send it out immediately
|
|
96
|
+
// in case the user is unhappy and closes the browser early
|
|
97
|
+
this.sendMetricsInQueue();
|
|
98
|
+
this.shouldSendMetricsOnNextStatsReport = false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// If a connection fails, send the rest of the metrics in queue and get a new connection id.
|
|
103
|
+
const parsedPayload = parseJsonPayload(data.payload);
|
|
104
|
+
if (
|
|
105
|
+
data.name === 'onconnectionstatechange' &&
|
|
106
|
+
parsedPayload &&
|
|
107
|
+
parsedPayload.value === 'failed'
|
|
108
|
+
) {
|
|
109
|
+
this.sendMetricsInQueue();
|
|
110
|
+
this.resetConnection();
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
console.error(e);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Clear the metrics interval.
|
|
120
|
+
*
|
|
121
|
+
* @returns {void}
|
|
122
|
+
*/
|
|
123
|
+
closeMetrics() {
|
|
124
|
+
this.sendMetricsInQueue();
|
|
125
|
+
clearInterval(this.intervalId);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Anonymize IP addresses.
|
|
130
|
+
*
|
|
131
|
+
* @param {array} stats - An RTCStatsReport organized into an array of strings.
|
|
132
|
+
* @returns {string}
|
|
133
|
+
*/
|
|
134
|
+
anonymizeIp(stats: string): string {
|
|
135
|
+
const data = JSON.parse(stats);
|
|
136
|
+
// on local and remote candidates, anonymize the last 4 bits.
|
|
137
|
+
if (data.type === 'local-candidate' || data.type === 'remote-candidate') {
|
|
138
|
+
data.ip = CallDiagnosticUtils.anonymizeIPAddress(data.ip) || undefined;
|
|
139
|
+
data.address = CallDiagnosticUtils.anonymizeIPAddress(data.address) || undefined;
|
|
140
|
+
data.relatedAddress =
|
|
141
|
+
CallDiagnosticUtils.anonymizeIPAddress(data.relatedAddress) || undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return JSON.stringify(data);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Set a new connection id.
|
|
149
|
+
*
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
private resetConnection() {
|
|
153
|
+
this.connectionId = uuid.v4();
|
|
154
|
+
this.shouldSendMetricsOnNextStatsReport = true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Send metrics to the metrics service.
|
|
159
|
+
*
|
|
160
|
+
* @returns {void}
|
|
161
|
+
*/
|
|
162
|
+
private sendMetrics() {
|
|
163
|
+
this.webex.request({
|
|
164
|
+
method: 'POST',
|
|
165
|
+
service: 'unifiedTelemetry',
|
|
166
|
+
resource: 'metric/v2',
|
|
167
|
+
headers: {
|
|
168
|
+
type: 'webrtcMedia',
|
|
169
|
+
appId: RTC_METRICS.APP_ID,
|
|
170
|
+
},
|
|
171
|
+
body: {
|
|
172
|
+
metrics: [
|
|
173
|
+
{
|
|
174
|
+
type: 'webrtc',
|
|
175
|
+
version: '1.1.0',
|
|
176
|
+
userId: this.webex.internal.device.userId,
|
|
177
|
+
meetingId: this.meetingId,
|
|
178
|
+
correlationId: this.correlationId,
|
|
179
|
+
connectionId: this.connectionId,
|
|
180
|
+
data: this.metricsQueue,
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import sinon from 'sinon';
|
|
2
|
+
import bowser from 'bowser';
|
|
2
3
|
import {assert} from '@webex/test-helper-chai';
|
|
3
4
|
import {WebexHttpError} from '@webex/webex-core';
|
|
4
5
|
import {BrowserDetection} from '@webex/common';
|
|
6
|
+
import window from 'global/window';
|
|
5
7
|
import {
|
|
6
8
|
CallDiagnosticLatencies,
|
|
7
9
|
CallDiagnosticMetrics,
|
|
@@ -1012,6 +1014,9 @@ describe('internal-plugin-metrics', () => {
|
|
|
1012
1014
|
const getIdentifiersSpy = sinon.spy(cd, 'getIdentifiers');
|
|
1013
1015
|
const getSubServiceTypeSpy = sinon.spy(cd, 'getSubServiceType');
|
|
1014
1016
|
const validatorSpy = sinon.spy(cd, 'validator');
|
|
1017
|
+
sinon.stub(window.navigator, 'userAgent').get(() => userAgent);
|
|
1018
|
+
sinon.stub(bowser, 'getParser').returns(userAgent);
|
|
1019
|
+
|
|
1015
1020
|
const options = {
|
|
1016
1021
|
meetingId: fakeMeeting.id,
|
|
1017
1022
|
mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
|
|
@@ -1040,7 +1045,7 @@ describe('internal-plugin-metrics', () => {
|
|
|
1040
1045
|
assert.deepEqual(webexLoggerLogCalls[2].args, [
|
|
1041
1046
|
'call-diagnostic-events -> ',
|
|
1042
1047
|
'CallDiagnosticMetrics: @createClientEventObjectInMeeting => collected browser data',
|
|
1043
|
-
|
|
1048
|
+
`${JSON.stringify(userAgent)}`,
|
|
1044
1049
|
]);
|
|
1045
1050
|
|
|
1046
1051
|
assert.deepEqual(webexLoggerLogCalls[3].args, [
|
|
@@ -2768,11 +2773,11 @@ describe('internal-plugin-metrics', () => {
|
|
|
2768
2773
|
// The method is called in beforeEach itself. We are just testing it here
|
|
2769
2774
|
it('sets the received deviceInfo to call-diagnostics', () => {
|
|
2770
2775
|
const webexLoggerLogCalls = webex.logger.log.getCalls();
|
|
2771
|
-
const device = {
|
|
2776
|
+
const device = {userId: 'userId', url: 'deviceUrl', orgId: 'orgId'};
|
|
2772
2777
|
|
|
2773
2778
|
assert.deepEqual(webexLoggerLogCalls[0].args, [
|
|
2774
2779
|
'CallDiagnosticMetrics: @setDeviceInfo called',
|
|
2775
|
-
device
|
|
2780
|
+
device,
|
|
2776
2781
|
]);
|
|
2777
2782
|
|
|
2778
2783
|
assert.deepEqual(cd.device, device);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import 'jsdom-global/register';
|
|
2
|
+
import RtcMetrics from '../../../../src/rtcMetrics';
|
|
3
|
+
import MockWebex from '@webex/test-helper-mock-webex';
|
|
4
|
+
import {assert} from '@webex/test-helper-chai';
|
|
5
|
+
import sinon from 'sinon';
|
|
6
|
+
import RTC_METRICS from '../../../../src/rtcMetrics/constants';
|
|
7
|
+
|
|
8
|
+
const FAKE_METRICS_ITEM = {payload: ['{"type":"string","value":"fake-metrics","id":""}']};
|
|
9
|
+
const FAILURE_METRICS_ITEM = {
|
|
10
|
+
name: "onconnectionstatechange",
|
|
11
|
+
payload: ['{"type":"string","value":"failed","id":""}'],
|
|
12
|
+
timestamp: 1707929986667
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const STATS_WITH_IP = '{"id":"RTCIceCandidate_/kQs0ZNU","type":"remote-candidate","transportId":"RTCTransport_0_1","isRemote":true,"ip":"11.22.111.255","address":"11.22.111.255","port":5004,"protocol":"udp","candidateType":"host","priority":2130706431}';
|
|
16
|
+
const STATS_WITH_IP_RESULT = '{"id":"RTCIceCandidate_/kQs0ZNU","type":"remote-candidate","transportId":"RTCTransport_0_1","isRemote":true,"ip":"11.22.111.240","address":"11.22.111.240","port":5004,"protocol":"udp","candidateType":"host","priority":2130706431}';
|
|
17
|
+
|
|
18
|
+
describe('RtcMetrics', () => {
|
|
19
|
+
let metrics: RtcMetrics;
|
|
20
|
+
let webex: MockWebex;
|
|
21
|
+
let clock;
|
|
22
|
+
let anonymizeIpSpy;
|
|
23
|
+
|
|
24
|
+
const sandbox = sinon.createSandbox();
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
clock = sinon.useFakeTimers();
|
|
28
|
+
window.setInterval = setInterval;
|
|
29
|
+
webex = new MockWebex();
|
|
30
|
+
metrics = new RtcMetrics(webex, 'mock-meeting-id', 'mock-correlation-id');
|
|
31
|
+
anonymizeIpSpy = sandbox.spy(metrics, 'anonymizeIp');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
sandbox.restore();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('sendMetrics should send a webex request', () => {
|
|
39
|
+
assert.notCalled(webex.request);
|
|
40
|
+
|
|
41
|
+
metrics.addMetrics(FAKE_METRICS_ITEM);
|
|
42
|
+
(metrics as any).sendMetrics();
|
|
43
|
+
|
|
44
|
+
assert.callCount(webex.request, 1);
|
|
45
|
+
assert.calledWithMatch(webex.request, sinon.match.has('headers', {
|
|
46
|
+
type: 'webrtcMedia',
|
|
47
|
+
appId: RTC_METRICS.APP_ID,
|
|
48
|
+
}));
|
|
49
|
+
assert.calledWithMatch(webex.request, sinon.match.hasNested('body.metrics[0].data[0].payload', FAKE_METRICS_ITEM.payload));
|
|
50
|
+
assert.calledWithMatch(webex.request, sinon.match.hasNested('body.metrics[0].meetingId', 'mock-meeting-id'));
|
|
51
|
+
assert.calledWithMatch(webex.request, sinon.match.hasNested('body.metrics[0].correlationId', 'mock-correlation-id'));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should have a defined sendMetricsInQueue function which is public', () => {
|
|
55
|
+
assert.isDefined(metrics.sendMetricsInQueue);
|
|
56
|
+
assert.isFunction(metrics.sendMetricsInQueue);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should send metrics requests over time', () => {
|
|
60
|
+
assert.notCalled(webex.request);
|
|
61
|
+
|
|
62
|
+
metrics.addMetrics(FAKE_METRICS_ITEM);
|
|
63
|
+
assert.deepEqual(metrics.metricsQueue, [FAKE_METRICS_ITEM]);
|
|
64
|
+
clock.tick(60 * 1000);
|
|
65
|
+
|
|
66
|
+
assert.callCount(webex.request, 1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should not send requests with no items in the queue', () => {
|
|
70
|
+
clock.tick(60 * 1000);
|
|
71
|
+
assert.notCalled(webex.request);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('sendMetricsInQueue should send metrics if any exist in the queue', () => {
|
|
75
|
+
assert.notCalled(webex.request);
|
|
76
|
+
|
|
77
|
+
metrics.addMetrics(FAKE_METRICS_ITEM);
|
|
78
|
+
(metrics as any).sendMetricsInQueue();
|
|
79
|
+
|
|
80
|
+
assert.callCount(webex.request, 1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should clear out metrics on close', () => {
|
|
84
|
+
assert.notCalled(webex.request);
|
|
85
|
+
|
|
86
|
+
metrics.addMetrics(FAKE_METRICS_ITEM);
|
|
87
|
+
metrics.closeMetrics();
|
|
88
|
+
|
|
89
|
+
assert.callCount(webex.request, 1);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should clear out metrics on failure', () => {
|
|
93
|
+
assert.notCalled(webex.request);
|
|
94
|
+
|
|
95
|
+
metrics.addMetrics(FAILURE_METRICS_ITEM);
|
|
96
|
+
|
|
97
|
+
assert.callCount(webex.request, 1);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should have the same connectionId on success', () => {
|
|
101
|
+
const originalId = metrics.connectionId;
|
|
102
|
+
|
|
103
|
+
metrics.addMetrics(FAKE_METRICS_ITEM);
|
|
104
|
+
|
|
105
|
+
assert.strictEqual(originalId, metrics.connectionId);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should have a new connectionId on failure', () => {
|
|
109
|
+
const originalId = metrics.connectionId;
|
|
110
|
+
|
|
111
|
+
metrics.addMetrics(FAILURE_METRICS_ITEM);
|
|
112
|
+
|
|
113
|
+
assert.notEqual(originalId, metrics.connectionId);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should anonymize IP addresses', () => {
|
|
117
|
+
assert.strictEqual(metrics.anonymizeIp(STATS_WITH_IP), STATS_WITH_IP_RESULT);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should call anonymizeIp', () => {
|
|
121
|
+
metrics.addMetrics({ name: 'stats-report', payload: [STATS_WITH_IP] });
|
|
122
|
+
assert.calledOnce(anonymizeIpSpy);
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should send metrics on first stats-report', () => {
|
|
126
|
+
assert.callCount(webex.request, 0);
|
|
127
|
+
|
|
128
|
+
metrics.addMetrics(FAKE_METRICS_ITEM);
|
|
129
|
+
assert.callCount(webex.request, 0);
|
|
130
|
+
|
|
131
|
+
// first stats-report should trigger a call to webex.request
|
|
132
|
+
metrics.addMetrics({ name: 'stats-report', payload: [STATS_WITH_IP] });
|
|
133
|
+
assert.callCount(webex.request, 1);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should send metrics on first stats-report after a new connection', () => {
|
|
137
|
+
assert.callCount(webex.request, 0);
|
|
138
|
+
|
|
139
|
+
// first stats-report should trigger a call to webex.request
|
|
140
|
+
metrics.addMetrics({ name: 'stats-report', payload: [STATS_WITH_IP] });
|
|
141
|
+
assert.callCount(webex.request, 1);
|
|
142
|
+
|
|
143
|
+
// subsequent stats-report doesn't trigger it
|
|
144
|
+
metrics.addMetrics({ name: 'stats-report', payload: [STATS_WITH_IP] });
|
|
145
|
+
assert.callCount(webex.request, 1);
|
|
146
|
+
|
|
147
|
+
// now, simulate a failure - that triggers a new connection and upload of the metrics
|
|
148
|
+
metrics.addMetrics(FAILURE_METRICS_ITEM);
|
|
149
|
+
assert.callCount(webex.request, 2);
|
|
150
|
+
|
|
151
|
+
// and another stats-report should trigger another upload of the metrics
|
|
152
|
+
metrics.addMetrics({ name: 'stats-report', payload: [STATS_WITH_IP] });
|
|
153
|
+
assert.callCount(webex.request, 3);
|
|
154
|
+
});
|
|
155
|
+
});
|