@webex/internal-plugin-metrics 3.0.0-bnr.5 → 3.0.0-next.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 (78) hide show
  1. package/.eslintrc.js +6 -0
  2. package/babel.config.js +3 -0
  3. package/dist/batcher.js +41 -3
  4. package/dist/batcher.js.map +1 -1
  5. package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js +64 -0
  6. package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js.map +1 -0
  7. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +474 -0
  8. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -0
  9. package/dist/call-diagnostic/call-diagnostic-metrics.js +850 -0
  10. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -0
  11. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +349 -0
  12. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -0
  13. package/dist/call-diagnostic/config.js +609 -0
  14. package/dist/call-diagnostic/config.js.map +1 -0
  15. package/dist/client-metrics-batcher.js +3 -3
  16. package/dist/client-metrics-batcher.js.map +1 -1
  17. package/dist/config.js +6 -9
  18. package/dist/config.js.map +1 -1
  19. package/dist/index.js +35 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/metrics.js +28 -22
  22. package/dist/metrics.js.map +1 -1
  23. package/dist/metrics.types.js +7 -0
  24. package/dist/metrics.types.js.map +1 -0
  25. package/dist/new-metrics.js +302 -0
  26. package/dist/new-metrics.js.map +1 -0
  27. package/dist/prelogin-metrics-batcher.js +81 -0
  28. package/dist/prelogin-metrics-batcher.js.map +1 -0
  29. package/dist/types/batcher.d.ts +5 -0
  30. package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +204 -0
  31. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +427 -0
  32. package/dist/types/call-diagnostic/call-diagnostic-metrics.util.d.ts +103 -0
  33. package/dist/types/call-diagnostic/config.d.ts +178 -0
  34. package/dist/types/config.d.ts +18 -0
  35. package/dist/types/index.d.ts +15 -3
  36. package/dist/types/metrics.d.ts +1 -0
  37. package/dist/types/metrics.types.d.ts +105 -0
  38. package/dist/types/new-metrics.d.ts +131 -0
  39. package/dist/types/prelogin-metrics-batcher.d.ts +2 -0
  40. package/dist/types/utils.d.ts +6 -0
  41. package/dist/utils.js +26 -0
  42. package/dist/utils.js.map +1 -0
  43. package/jest.config.js +3 -0
  44. package/package.json +34 -10
  45. package/process +1 -0
  46. package/src/batcher.js +38 -0
  47. package/src/call-diagnostic/call-diagnostic-metrics-batcher.ts +72 -0
  48. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +435 -0
  49. package/src/call-diagnostic/call-diagnostic-metrics.ts +913 -0
  50. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +392 -0
  51. package/src/call-diagnostic/config.ts +685 -0
  52. package/src/client-metrics-batcher.js +1 -0
  53. package/src/config.js +1 -0
  54. package/src/index.ts +54 -0
  55. package/src/metrics.js +20 -16
  56. package/src/metrics.types.ts +168 -0
  57. package/src/new-metrics.ts +278 -0
  58. package/src/prelogin-metrics-batcher.ts +95 -0
  59. package/src/utils.ts +17 -0
  60. package/test/unit/spec/batcher.js +2 -0
  61. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +458 -0
  62. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +520 -0
  63. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +2297 -0
  64. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +628 -0
  65. package/test/unit/spec/client-metrics-batcher.js +2 -0
  66. package/test/unit/spec/metrics.js +76 -95
  67. package/test/unit/spec/new-metrics.ts +233 -0
  68. package/test/unit/spec/prelogin-metrics-batcher.ts +250 -0
  69. package/test/unit/spec/utils.ts +22 -0
  70. package/tsconfig.json +6 -0
  71. package/dist/call-diagnostic-events-batcher.js +0 -60
  72. package/dist/call-diagnostic-events-batcher.js.map +0 -1
  73. package/dist/internal-plugin-metrics.d.ts +0 -21
  74. package/dist/tsdoc-metadata.json +0 -11
  75. package/src/call-diagnostic-events-batcher.js +0 -62
  76. package/src/index.js +0 -17
  77. package/test/unit/spec/call-diagnostic-events-batcher.js +0 -195
  78. package/dist/types/{call-diagnostic-events-batcher.d.ts → call-diagnostic/call-diagnostic-metrics-batcher.d.ts} +1 -1
package/src/index.ts ADDED
@@ -0,0 +1,54 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import '@webex/internal-plugin-device';
6
+
7
+ import {registerInternalPlugin} from '@webex/webex-core';
8
+
9
+ import Metrics from './metrics';
10
+ import config from './config';
11
+ import NewMetrics from './new-metrics';
12
+ import * as Utils from './utils';
13
+ import {
14
+ ClientEvent,
15
+ ClientEventLeaveReason,
16
+ SubmitBehavioralEvent,
17
+ SubmitClientEvent,
18
+ SubmitInternalEvent,
19
+ SubmitOperationalEvent,
20
+ SubmitMQE,
21
+ } from './metrics.types';
22
+ import * as CALL_DIAGNOSTIC_CONFIG from './call-diagnostic/config';
23
+ import * as CallDiagnosticUtils from './call-diagnostic/call-diagnostic-metrics.util';
24
+ import CallDiagnosticMetrics from './call-diagnostic/call-diagnostic-metrics';
25
+ import CallDiagnosticLatencies from './call-diagnostic/call-diagnostic-metrics-latencies';
26
+
27
+ registerInternalPlugin('metrics', Metrics, {
28
+ config,
29
+ });
30
+
31
+ registerInternalPlugin('newMetrics', NewMetrics, {
32
+ config,
33
+ });
34
+
35
+ export {default, getOSNameInternal} from './metrics';
36
+
37
+ export {
38
+ config,
39
+ CALL_DIAGNOSTIC_CONFIG,
40
+ NewMetrics,
41
+ Utils,
42
+ CallDiagnosticUtils,
43
+ CallDiagnosticLatencies,
44
+ CallDiagnosticMetrics,
45
+ };
46
+ export type {
47
+ ClientEvent,
48
+ ClientEventLeaveReason,
49
+ SubmitBehavioralEvent,
50
+ SubmitClientEvent,
51
+ SubmitInternalEvent,
52
+ SubmitMQE,
53
+ SubmitOperationalEvent,
54
+ };
package/src/metrics.js CHANGED
@@ -10,7 +10,6 @@ import {OS_NAME, OSMap, CLIENT_NAME} from './config';
10
10
 
11
11
  import Batcher from './batcher';
12
12
  import ClientMetricsBatcher from './client-metrics-batcher';
13
- import CallDiagnosticEventsBatcher from './call-diagnostic-events-batcher';
14
13
 
15
14
  const {getOSName, getOSVersion, getBrowserName, getBrowserVersion} = BrowserDetection();
16
15
 
@@ -38,7 +37,6 @@ const Metrics = WebexPlugin.extend({
38
37
  children: {
39
38
  batcher: Batcher,
40
39
  clientMetricsBatcher: ClientMetricsBatcher,
41
- callDiagnosticEventsBatcher: CallDiagnosticEventsBatcher,
42
40
  },
43
41
 
44
42
  namespace: 'Metrics',
@@ -48,22 +46,24 @@ const Metrics = WebexPlugin.extend({
48
46
  },
49
47
 
50
48
  /**
51
- * This corresponds to #sendSemiStructured() in the deprecated metrics handler
49
+ * Returns the payload for submitting client metrics.
52
50
  * @param {string} eventName
53
- * @param {Object} props
54
- * @param {string} preLoginId
55
- * @returns {Object} HttpResponse object
51
+ * @param {any} props
52
+ * @returns {any} - the payload
56
53
  */
57
- submitClientMetrics(eventName, props = {}, preLoginId) {
54
+ getClientMetricsPayload(eventName, props) {
58
55
  if (!eventName) {
59
56
  throw Error('Missing behavioral metric name. Please provide one');
60
57
  }
61
58
  const payload = {metricName: eventName};
59
+ // @ts-ignore
60
+ const providedClientVersion = this.webex.meetings?.config?.metrics?.clientVersion;
62
61
 
63
62
  payload.tags = {
64
63
  ...props.tags,
65
64
  browser: getBrowserName(),
66
65
  os: getOSNameInternal(),
66
+ appVersion: providedClientVersion,
67
67
 
68
68
  // Node does not like this so we need to check if it exists or not
69
69
  // eslint-disable-next-line no-undef
@@ -103,6 +103,19 @@ const Metrics = WebexPlugin.extend({
103
103
  // is impossible so unable to use Date.now()
104
104
  payload.timestamp = new Date().valueOf();
105
105
 
106
+ return payload;
107
+ },
108
+
109
+ /**
110
+ * This corresponds to #sendSemiStructured() in the deprecated metrics handler
111
+ * @param {string} eventName
112
+ * @param {Object} props
113
+ * @param {string} preLoginId
114
+ * @returns {Object} HttpResponse object
115
+ */
116
+ submitClientMetrics(eventName, props = {}, preLoginId) {
117
+ const payload = this.getClientMetricsPayload(eventName, props);
118
+
106
119
  if (preLoginId) {
107
120
  const _payload = {
108
121
  metrics: [payload],
@@ -151,15 +164,6 @@ const Metrics = WebexPlugin.extend({
151
164
  })
152
165
  );
153
166
  },
154
-
155
- submitCallDiagnosticEvents(payload) {
156
- const event = {
157
- type: 'diagnostic-event',
158
- eventPayload: payload,
159
- };
160
-
161
- return this.callDiagnosticEventsBatcher.request(event);
162
- },
163
167
  });
164
168
 
165
169
  export default Metrics;
@@ -0,0 +1,168 @@
1
+ import {
2
+ ClientEvent as RawClientEvent,
3
+ Event as RawEvent,
4
+ MediaQualityEvent as RawMediaQualityEvent,
5
+ } from '@webex/event-dictionary-ts';
6
+
7
+ export type Event = Omit<RawEvent, 'event'> & {event: RawClientEvent | RawMediaQualityEvent};
8
+
9
+ export type ClientEventError = NonNullable<RawClientEvent['errors']>[0];
10
+
11
+ export type EnvironmentType = NonNullable<RawEvent['origin']['environment']>;
12
+
13
+ export type NewEnvironmentType = NonNullable<RawEvent['origin']['newEnvironment']>;
14
+
15
+ export type ClientLaunchMethodType = NonNullable<
16
+ RawEvent['origin']['clientInfo']
17
+ >['clientLaunchMethod'];
18
+
19
+ export type SubmitClientEventOptions = {
20
+ meetingId?: string;
21
+ mediaConnections?: any[];
22
+ rawError?: any;
23
+ correlationId?: string;
24
+ preLoginId?: string;
25
+ environment?: EnvironmentType;
26
+ newEnvironmentType?: NewEnvironmentType;
27
+ clientLaunchMethod?: ClientLaunchMethodType;
28
+ webexConferenceIdStr?: string;
29
+ globalMeetingId?: string;
30
+ };
31
+
32
+ export type SubmitMQEOptions = {
33
+ meetingId: string;
34
+ mediaConnections?: any[];
35
+ networkType?: Event['origin']['networkType'];
36
+ webexConferenceIdStr?: string;
37
+ globalMeetingId?: string;
38
+ };
39
+
40
+ export type InternalEvent = {
41
+ name:
42
+ | 'internal.client.meetinginfo.request'
43
+ | 'internal.client.meetinginfo.response'
44
+ | 'internal.register.device.request'
45
+ | 'internal.register.device.response'
46
+ | 'internal.reset.join.latencies'
47
+ | 'internal.client.meeting.click.joinbutton'
48
+ | 'internal.host.meeting.participant.admitted'
49
+ | 'internal.client.meeting.interstitial-window.showed'
50
+ | 'internal.client.interstitial-window.click.joinbutton'
51
+ | 'internal.client.add-media.turn-discovery.start'
52
+ | 'internal.client.add-media.turn-discovery.end';
53
+
54
+ payload?: never;
55
+ options?: never;
56
+ };
57
+
58
+ export interface ClientEvent {
59
+ name: RawClientEvent['name'];
60
+ payload?: RawClientEvent;
61
+ options?: SubmitClientEventOptions;
62
+ }
63
+
64
+ export interface BehavioralEvent {
65
+ // TODO: not implemented
66
+ name: 'host.meeting.participant.admitted' | 'sdk.media-flow.started';
67
+ payload?: never;
68
+ options?: never;
69
+ }
70
+
71
+ export interface OperationalEvent {
72
+ // TODO: not implemented
73
+ name: never;
74
+ payload?: never;
75
+ options?: never;
76
+ }
77
+
78
+ export interface FeatureEvent {
79
+ // TODO: not implemented
80
+ name: never;
81
+ payload?: never;
82
+ options?: never;
83
+ }
84
+
85
+ export interface MediaQualityEvent {
86
+ name: RawMediaQualityEvent['name'];
87
+ payload?: RawMediaQualityEvent;
88
+ options: SubmitMQEOptions;
89
+ }
90
+
91
+ export type RecursivePartial<T> = {
92
+ [P in keyof T]?: T[P] extends (infer U)[]
93
+ ? RecursivePartial<U>[]
94
+ : T[P] extends object
95
+ ? RecursivePartial<T[P]>
96
+ : T[P];
97
+ };
98
+
99
+ export type MetricEventNames =
100
+ | InternalEvent['name']
101
+ | ClientEvent['name']
102
+ | BehavioralEvent['name']
103
+ | OperationalEvent['name']
104
+ | FeatureEvent['name']
105
+ | MediaQualityEvent['name'];
106
+
107
+ export type ClientInfo = NonNullable<RawEvent['origin']['clientInfo']>;
108
+ export type ClientType = NonNullable<RawEvent['origin']['clientInfo']>['clientType'];
109
+ export type SubClientType = NonNullable<RawEvent['origin']['clientInfo']>['subClientType'];
110
+ export type NetworkType = NonNullable<RawEvent['origin']>['networkType'];
111
+
112
+ export type ClientSubServiceType = ClientEvent['payload']['webexSubServiceType'];
113
+ export type ClientEventPayload = RecursivePartial<ClientEvent['payload']>;
114
+ export type ClientEventLeaveReason = ClientEvent['payload']['leaveReason'];
115
+ export type ClientEventPayloadError = ClientEvent['payload']['errors'];
116
+
117
+ export type MediaQualityEventAudioSetupDelayPayload = NonNullable<
118
+ MediaQualityEvent['payload']
119
+ >['audioSetupDelay'];
120
+ export type MediaQualityEventVideoSetupDelayPayload = NonNullable<
121
+ MediaQualityEvent['payload']
122
+ >['videoSetupDelay'];
123
+
124
+ export type SubmitMQEPayload = RecursivePartial<MediaQualityEvent['payload']> & {
125
+ intervals: NonNullable<MediaQualityEvent['payload']>['intervals'];
126
+ };
127
+
128
+ export type SubmitInternalEvent = (args: {
129
+ name: InternalEvent['name'];
130
+ payload?: RecursivePartial<InternalEvent['payload']>;
131
+ options?: any;
132
+ }) => void;
133
+
134
+ export type SubmitBehavioralEvent = (args: {
135
+ name: BehavioralEvent['name'];
136
+ payload?: RecursivePartial<BehavioralEvent['payload']>;
137
+ options?: any;
138
+ }) => void;
139
+
140
+ export type SubmitClientEvent = (args: {
141
+ name: ClientEvent['name'];
142
+ payload?: RecursivePartial<ClientEvent['payload']>;
143
+ options?: SubmitClientEventOptions;
144
+ }) => Promise<any>;
145
+
146
+ export type SubmitOperationalEvent = (args: {
147
+ name: OperationalEvent['name'];
148
+ payload?: RecursivePartial<OperationalEvent['payload']>;
149
+ options?: any;
150
+ }) => void;
151
+
152
+ export type SubmitMQE = (args: {
153
+ name: MediaQualityEvent['name'];
154
+ payload: SubmitMQEPayload;
155
+ options: any;
156
+ }) => void;
157
+
158
+ export type BuildClientEventFetchRequestOptions = (args: {
159
+ name: ClientEvent['name'];
160
+ payload?: RecursivePartial<ClientEvent['payload']>;
161
+ options?: SubmitClientEventOptions;
162
+ }) => Promise<any>;
163
+
164
+ export type PreComputedLatencies =
165
+ | 'internal.client.pageJMT'
166
+ | 'internal.download.time'
167
+ | 'internal.click.to.interstitial'
168
+ | 'internal.call.init.join.req';
@@ -0,0 +1,278 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable class-methods-use-this */
3
+ /* eslint-disable valid-jsdoc */
4
+
5
+ // @ts-ignore
6
+ import {WebexPlugin} from '@webex/webex-core';
7
+
8
+ import CallDiagnosticMetrics from './call-diagnostic/call-diagnostic-metrics';
9
+ import {
10
+ RecursivePartial,
11
+ ClientEvent,
12
+ FeatureEvent,
13
+ BehavioralEvent,
14
+ OperationalEvent,
15
+ MediaQualityEvent,
16
+ InternalEvent,
17
+ SubmitClientEventOptions,
18
+ } from './metrics.types';
19
+ import CallDiagnosticLatencies from './call-diagnostic/call-diagnostic-metrics-latencies';
20
+ import {setMetricTimings} from './call-diagnostic/call-diagnostic-metrics.util';
21
+ import {generateCommonErrorMetadata} from './utils';
22
+
23
+ /**
24
+ * Metrics plugin to centralize all types of metrics.
25
+ * @class
26
+ */
27
+ class Metrics extends WebexPlugin {
28
+ // eslint-disable-next-line no-use-before-define
29
+ static instance: Metrics;
30
+
31
+ // Call Diagnostic latencies
32
+ callDiagnosticLatencies: CallDiagnosticLatencies;
33
+ // Helper classes to handle the different types of metrics
34
+ callDiagnosticMetrics: CallDiagnosticMetrics;
35
+
36
+ /**
37
+ * Constructor
38
+ * @param args
39
+ * @constructor
40
+ * @private
41
+ * @returns
42
+ */
43
+ constructor(...args) {
44
+ super(...args);
45
+
46
+ this.onReady();
47
+ }
48
+
49
+ /**
50
+ * On Ready
51
+ */
52
+ private onReady() {
53
+ // @ts-ignore
54
+ this.webex.once('ready', () => {
55
+ // @ts-ignore
56
+ this.callDiagnosticMetrics = new CallDiagnosticMetrics({}, {parent: this.webex});
57
+ // @ts-ignore
58
+ this.callDiagnosticLatencies = new CallDiagnosticLatencies({}, {parent: this.webex});
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Used for internal purposes only
64
+ * @param args
65
+ */
66
+ submitInternalEvent({
67
+ name,
68
+ payload,
69
+ options,
70
+ }: {
71
+ name: InternalEvent['name'];
72
+ payload?: RecursivePartial<InternalEvent['payload']>;
73
+ options?: any;
74
+ }) {
75
+ if (name === 'internal.reset.join.latencies') {
76
+ this.callDiagnosticLatencies.clearTimestamps();
77
+ } else {
78
+ this.callDiagnosticLatencies.saveTimestamp({key: name});
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Behavioral event
84
+ * @param args
85
+ */
86
+ submitBehavioralEvent({
87
+ name,
88
+ payload,
89
+ options,
90
+ }: {
91
+ name: BehavioralEvent['name'];
92
+ payload?: RecursivePartial<BehavioralEvent['payload']>;
93
+ options?: any;
94
+ }) {
95
+ this.callDiagnosticLatencies.saveTimestamp({key: name});
96
+ throw new Error('Not implemented.');
97
+ }
98
+
99
+ /**
100
+ * Operational event
101
+ * @param args
102
+ */
103
+ submitOperationalEvent({
104
+ name,
105
+ payload,
106
+ options,
107
+ }: {
108
+ name: OperationalEvent['name'];
109
+ payload?: RecursivePartial<OperationalEvent['payload']>;
110
+ options?: any;
111
+ }) {
112
+ throw new Error('Not implemented.');
113
+ }
114
+
115
+ /**
116
+ * Call Analyzer: Media Quality Event
117
+ * @param args
118
+ */
119
+ submitMQE({
120
+ name,
121
+ payload,
122
+ options,
123
+ }: {
124
+ name: MediaQualityEvent['name'];
125
+ payload: RecursivePartial<MediaQualityEvent['payload']> & {
126
+ intervals: MediaQualityEvent['payload']['intervals'];
127
+ };
128
+ options: any;
129
+ }) {
130
+ this.callDiagnosticLatencies.saveTimestamp({key: name});
131
+ this.callDiagnosticMetrics.submitMQE({name, payload, options});
132
+ }
133
+
134
+ /**
135
+ * Call Analyzer: Feature Usage Event
136
+ * @param args
137
+ */
138
+ submitFeatureEvent({
139
+ name,
140
+ payload,
141
+ options,
142
+ }: {
143
+ name: FeatureEvent['name'];
144
+ payload?: RecursivePartial<FeatureEvent['payload']>;
145
+ options: any;
146
+ }) {
147
+ throw new Error('Not implemented.');
148
+ }
149
+
150
+ /**
151
+ * Call Analyzer: Client Event
152
+ * @public
153
+ * @param args
154
+ */
155
+ public submitClientEvent({
156
+ name,
157
+ payload,
158
+ options,
159
+ }: {
160
+ name: ClientEvent['name'];
161
+ payload?: RecursivePartial<ClientEvent['payload']>;
162
+ options?: SubmitClientEventOptions;
163
+ }): Promise<any> {
164
+ if (!this.callDiagnosticLatencies || !this.callDiagnosticMetrics) {
165
+ // @ts-ignore
166
+ this.webex.logger.log(
167
+ `NewMetrics: @submitClientEvent. Attempted to submit before webex.ready. Event name: ${name}`
168
+ );
169
+
170
+ return Promise.resolve();
171
+ }
172
+ this.callDiagnosticLatencies.saveTimestamp({
173
+ key: name,
174
+ options: {meetingId: options?.meetingId},
175
+ });
176
+
177
+ return this.callDiagnosticMetrics.submitClientEvent({name, payload, options});
178
+ }
179
+
180
+ /**
181
+ * Issue request to alias a user's pre-login ID with their CI UUID
182
+ * @param {string} preLoginId
183
+ * @returns {Object} HttpResponse object
184
+ */
185
+ public clientMetricsAliasUser(preLoginId: string) {
186
+ // @ts-ignore
187
+ return this.webex
188
+ .request({
189
+ method: 'POST',
190
+ api: 'metrics',
191
+ resource: 'clientmetrics',
192
+ headers: {
193
+ 'x-prelogin-userid': preLoginId,
194
+ },
195
+ body: {},
196
+ qs: {
197
+ alias: true,
198
+ },
199
+ })
200
+ .then((res) => {
201
+ // @ts-ignore
202
+ this.webex.logger.log(`NewMetrics: @clientMetricsAliasUser. Request successful.`);
203
+
204
+ return res;
205
+ })
206
+ .catch((err) => {
207
+ // @ts-ignore
208
+ this.logger.error(
209
+ `NewMetrics: @clientMetricsAliasUser. Request failed:`,
210
+ `err: ${generateCommonErrorMetadata(err)}`
211
+ );
212
+
213
+ return Promise.reject(err);
214
+ });
215
+ }
216
+
217
+ /**
218
+ * Returns a promise that will resolve to fetch options for submitting a metric.
219
+ *
220
+ * This is to support quickly submitting metrics when the browser/tab is closing.
221
+ * Calling submitClientEvent will not work because there some async steps that will
222
+ * not complete before the browser is closed. Instead, we pre-gather all the
223
+ * information/options needed for the request(s), and then simply and quickly
224
+ * fire the fetch(es) when beforeUnload is triggered.
225
+ *
226
+ * We must use fetch instead of request because fetch has a keepalive option that
227
+ * allows the request it to outlive the page.
228
+ *
229
+ * Note: the timings values will be wrong, but setMetricTimingsAndFetch() will
230
+ * properly adjust them before submitting.
231
+ *
232
+ * @public
233
+ * @param {Object} arg
234
+ * @param {String} arg.name - event name
235
+ * @param {Object} arg.payload - event payload
236
+ * @param {Object} arg.options - other options
237
+ * @returns {Promise} promise that resolves to options to be used with fetch
238
+ */
239
+ public async buildClientEventFetchRequestOptions({
240
+ name,
241
+ payload,
242
+ options,
243
+ }: {
244
+ name: ClientEvent['name'];
245
+ payload?: RecursivePartial<ClientEvent['payload']>;
246
+ options?: SubmitClientEventOptions;
247
+ }): Promise<any> {
248
+ return this.callDiagnosticMetrics.buildClientEventFetchRequestOptions({
249
+ name,
250
+ payload,
251
+ options,
252
+ });
253
+ }
254
+
255
+ /**
256
+ * Submits a metric from pre-built request options via the fetch API. Updates
257
+ * the "$timings" and "originTime" values to Date.now() since the existing times
258
+ * were set when the options were built (not submitted).
259
+
260
+ * @param {any} options - the pre-built request options for submitting a metric
261
+ * @returns {Promise} promise that resolves to the response object
262
+ */
263
+ public setMetricTimingsAndFetch(options: any): Promise<any> {
264
+ // @ts-ignore
265
+ return this.webex.setTimingsAndFetch(setMetricTimings(options));
266
+ }
267
+
268
+ /**
269
+ * Returns true if the specified serviceErrorCode maps to an expected error.
270
+ * @param {number} serviceErrorCode the service error code
271
+ * @returns {boolean}
272
+ */
273
+ public isServiceErrorExpected(serviceErrorCode: number): boolean {
274
+ return this.callDiagnosticMetrics.isServiceErrorExpected(serviceErrorCode);
275
+ }
276
+ }
277
+
278
+ export default Metrics;
@@ -0,0 +1,95 @@
1
+ import {uniqueId} from 'lodash';
2
+ import Batcher from './batcher';
3
+ import {prepareDiagnosticMetricItem} from './call-diagnostic/call-diagnostic-metrics.util';
4
+ import {generateCommonErrorMetadata} from './utils';
5
+
6
+ const PRE_LOGIN_METRICS_IDENTIFIER = 'Pre Login Metrics -->';
7
+
8
+ const PreLoginMetricsBatcher = Batcher.extend({
9
+ namespace: 'Metrics',
10
+ preLoginId: undefined,
11
+
12
+ /**
13
+ * Save the pre-login ID.
14
+ * @param {string} preLoginId The pre-login ID to be saved.
15
+ * @returns {void}
16
+ */
17
+ savePreLoginId(preLoginId) {
18
+ this.preLoginId = preLoginId;
19
+ },
20
+
21
+ /**
22
+ * Prepare item
23
+ * @param {any} item
24
+ * @returns {Promise<any>}
25
+ */
26
+ prepareItem(item) {
27
+ return Promise.resolve(prepareDiagnosticMetricItem(this.webex, item));
28
+ },
29
+
30
+ /**
31
+ * Prepare request, add time sensitive date etc.
32
+ * @param {any[]} queue
33
+ * @returns {Promise<any[]>}
34
+ */
35
+ prepareRequest(queue) {
36
+ // Add sent timestamp
37
+ queue.forEach((item) => {
38
+ item.eventPayload.originTime = item.eventPayload.originTime || {};
39
+ item.eventPayload.originTime.sent = new Date().toISOString();
40
+ });
41
+
42
+ return Promise.resolve(queue);
43
+ },
44
+
45
+ /**
46
+ *
47
+ * @param {any} payload
48
+ * @returns {Promise<any>}
49
+ */
50
+ submitHttpRequest(payload: any) {
51
+ const batchId = uniqueId('prelogin-ca-batch-');
52
+ if (this.preLoginId === undefined) {
53
+ this.webex.logger.error(
54
+ PRE_LOGIN_METRICS_IDENTIFIER,
55
+ `PreLoginMetricsBatcher: @submitHttpRequest#${batchId}. PreLoginId is not set.`
56
+ );
57
+
58
+ return Promise.reject(new Error('PreLoginId is not set.'));
59
+ }
60
+
61
+ return this.webex
62
+ .request({
63
+ method: 'POST',
64
+ service: 'metrics',
65
+ resource: 'clientmetrics-prelogin',
66
+ headers: {
67
+ authorization: false,
68
+ 'x-prelogin-userid': this.preLoginId,
69
+ },
70
+ body: {
71
+ metrics: payload,
72
+ },
73
+ waitForServiceTimeout: this.webex.config.metrics.waitForServiceTimeout,
74
+ })
75
+ .then((res) => {
76
+ this.webex.logger.log(
77
+ PRE_LOGIN_METRICS_IDENTIFIER,
78
+ `PreLoginMetricsBatcher: @submitHttpRequest#${batchId}. Request successful.`
79
+ );
80
+
81
+ return res;
82
+ })
83
+ .catch((err) => {
84
+ this.webex.logger.error(
85
+ PRE_LOGIN_METRICS_IDENTIFIER,
86
+ `PreLoginMetricsBatcher: @submitHttpRequest#${batchId}. Request failed:`,
87
+ `error: ${generateCommonErrorMetadata(err)}`
88
+ );
89
+
90
+ return Promise.reject(err);
91
+ });
92
+ },
93
+ });
94
+
95
+ export default PreLoginMetricsBatcher;
package/src/utils.ts ADDED
@@ -0,0 +1,17 @@
1
+ /* eslint-disable import/prefer-default-export */
2
+ /**
3
+ * Generates common metadata for errors
4
+ * @param {any} error
5
+ * @returns {object}
6
+ */
7
+ export const generateCommonErrorMetadata = (error) => {
8
+ if (error instanceof Error) {
9
+ return JSON.stringify({
10
+ message: error?.message,
11
+ name: error?.name,
12
+ stack: error?.stack,
13
+ });
14
+ }
15
+
16
+ return error;
17
+ };
@@ -37,6 +37,7 @@ describe('plugin-metrics', () => {
37
37
  return Promise.resolve({
38
38
  statusCode: 204,
39
39
  body: undefined,
40
+ waitForServiceTimeout: 30,
40
41
  options,
41
42
  });
42
43
  };
@@ -97,6 +98,7 @@ describe('plugin-metrics', () => {
97
98
  return Promise.resolve({
98
99
  statusCode: 204,
99
100
  body: undefined,
101
+ waitForServiceTimeout: 30,
100
102
  options,
101
103
  });
102
104
  });