@webex/internal-plugin-metrics 3.0.0-beta.27 → 3.0.0-beta.271

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/dist/call-diagnostic/call-diagnostic-metrics-batcher.js +65 -0
  2. package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js.map +1 -0
  3. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +456 -0
  4. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -0
  5. package/dist/call-diagnostic/call-diagnostic-metrics.js +744 -0
  6. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -0
  7. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +310 -0
  8. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -0
  9. package/dist/call-diagnostic/config.js +575 -0
  10. package/dist/call-diagnostic/config.js.map +1 -0
  11. package/dist/config.js +20 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/index.js +30 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/metrics.js +30 -30
  16. package/dist/metrics.js.map +1 -1
  17. package/dist/metrics.types.js +7 -0
  18. package/dist/metrics.types.js.map +1 -0
  19. package/dist/new-metrics.js +333 -0
  20. package/dist/new-metrics.js.map +1 -0
  21. package/dist/types/batcher.d.ts +2 -0
  22. package/dist/types/call-diagnostic/call-diagnostic-metrics-batcher.d.ts +2 -0
  23. package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +194 -0
  24. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +392 -0
  25. package/dist/types/call-diagnostic/call-diagnostic-metrics.util.d.ts +87 -0
  26. package/dist/types/call-diagnostic/config.d.ts +93 -0
  27. package/dist/types/client-metrics-batcher.d.ts +2 -0
  28. package/dist/types/config.d.ts +35 -0
  29. package/dist/types/index.d.ts +13 -0
  30. package/dist/types/metrics.d.ts +3 -0
  31. package/dist/types/metrics.types.d.ts +99 -0
  32. package/dist/types/new-metrics.d.ts +139 -0
  33. package/dist/types/utils.d.ts +6 -0
  34. package/dist/utils.js +27 -0
  35. package/dist/utils.js.map +1 -0
  36. package/package.json +13 -8
  37. package/src/call-diagnostic/call-diagnostic-metrics-batcher.ts +82 -0
  38. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +414 -0
  39. package/src/call-diagnostic/call-diagnostic-metrics.ts +808 -0
  40. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +320 -0
  41. package/src/call-diagnostic/config.ts +632 -0
  42. package/src/config.js +19 -0
  43. package/src/index.ts +43 -0
  44. package/src/metrics.js +25 -27
  45. package/src/metrics.types.ts +155 -0
  46. package/src/new-metrics.ts +317 -0
  47. package/src/utils.ts +17 -0
  48. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +394 -0
  49. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +477 -0
  50. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +1607 -0
  51. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +541 -0
  52. package/test/unit/spec/metrics.js +65 -97
  53. package/test/unit/spec/new-metrics.ts +269 -0
  54. package/test/unit/spec/utils.ts +22 -0
  55. package/tsconfig.json +6 -0
  56. package/dist/call-diagnostic-events-batcher.js +0 -60
  57. package/dist/call-diagnostic-events-batcher.js.map +0 -1
  58. package/src/call-diagnostic-events-batcher.js +0 -62
  59. package/src/index.js +0 -17
  60. package/test/unit/spec/call-diagnostic-events-batcher.js +0 -195
@@ -0,0 +1,808 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable class-methods-use-this */
3
+ /* eslint-disable valid-jsdoc */
4
+ import {getOSNameInternal} from '@webex/internal-plugin-metrics';
5
+ import {BrowserDetection} from '@webex/common';
6
+ import uuid from 'uuid';
7
+ import {merge} from 'lodash';
8
+ import {StatelessWebexPlugin} from '@webex/webex-core';
9
+
10
+ import {
11
+ anonymizeIPAddress,
12
+ clearEmptyKeysRecursively,
13
+ isLocusServiceErrorCode,
14
+ prepareDiagnosticMetricItem,
15
+ userAgentToString,
16
+ extractVersionMetadata,
17
+ isMeetingInfoServiceError,
18
+ isBrowserMediaErrorName,
19
+ isNetworkError,
20
+ isUnauthorizedError,
21
+ } from './call-diagnostic-metrics.util';
22
+ import {CLIENT_NAME} from '../config';
23
+ import {
24
+ Event,
25
+ ClientType,
26
+ SubClientType,
27
+ NetworkType,
28
+ EnvironmentType,
29
+ NewEnvironmentType,
30
+ ClientEvent,
31
+ SubmitClientEventOptions,
32
+ MediaQualityEvent,
33
+ SubmitMQEOptions,
34
+ SubmitMQEPayload,
35
+ ClientLaunchMethodType,
36
+ ClientEventError,
37
+ ClientEventPayload,
38
+ ClientInfo,
39
+ ClientEventPayloadError,
40
+ } from '../metrics.types';
41
+ import CallDiagnosticEventsBatcher from './call-diagnostic-metrics-batcher';
42
+ import {
43
+ CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD,
44
+ CALL_DIAGNOSTIC_EVENT_FAILED_TO_SEND,
45
+ NEW_LOCUS_ERROR_CLIENT_CODE,
46
+ SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP,
47
+ UNKNOWN_ERROR,
48
+ BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP,
49
+ MEETING_INFO_LOOKUP_ERROR_CLIENT_CODE,
50
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
51
+ NETWORK_ERROR,
52
+ AUTHENTICATION_FAILED_CODE,
53
+ } from './config';
54
+ import {generateCommonErrorMetadata} from '../utils';
55
+
56
+ const {getOSVersion, getBrowserName, getBrowserVersion} = BrowserDetection();
57
+
58
+ type GetOriginOptions = {
59
+ clientType: ClientType;
60
+ subClientType: SubClientType;
61
+ networkType?: NetworkType;
62
+ clientLaunchMethod?: ClientLaunchMethodType;
63
+ environment?: EnvironmentType;
64
+ newEnvironment?: NewEnvironmentType;
65
+ };
66
+
67
+ type GetIdentifiersOptions = {
68
+ meeting?: any;
69
+ mediaConnections?: any[];
70
+ correlationId?: string;
71
+ };
72
+
73
+ /**
74
+ * @description Util class to handle Call Analyzer Metrics
75
+ * @export
76
+ * @class CallDiagnosticMetrics
77
+ */
78
+ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
79
+ // @ts-ignore
80
+ private callDiagnosticEventsBatcher: CallDiagnosticEventsBatcher;
81
+ private logger: any; // to avoid adding @ts-ignore everywhere
82
+
83
+ /**
84
+ * Constructor
85
+ * @param args
86
+ */
87
+ constructor(...args) {
88
+ super(...args);
89
+ // @ts-ignore
90
+ this.logger = this.webex.logger;
91
+ // @ts-ignore
92
+ this.callDiagnosticEventsBatcher = new CallDiagnosticEventsBatcher({}, {parent: this.webex});
93
+ }
94
+
95
+ /**
96
+ * Returns the login type of the current user
97
+ * @returns one of 'login-ci','unverified-guest', null
98
+ */
99
+ getCurLoginType() {
100
+ // @ts-ignore
101
+ if (this.webex.canAuthorize) {
102
+ // @ts-ignore
103
+ return this.webex.credentials.isUnverifiedGuest ? 'unverified-guest' : 'login-ci';
104
+ }
105
+
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * Returns if the meeting has converged architecture enabled
111
+ * @param options.meetingId
112
+ */
113
+ getIsConvergedArchitectureEnabled({meetingId}: {meetingId?: string}): boolean {
114
+ if (meetingId) {
115
+ // @ts-ignore
116
+ const meeting = this.webex.meetings.meetingCollection.get(meetingId);
117
+
118
+ return meeting?.meetingInfo?.enableConvergedArchitecture;
119
+ }
120
+
121
+ return undefined;
122
+ }
123
+
124
+ /**
125
+ * Get origin object for Call Diagnostic Event payload.
126
+ * @param options
127
+ * @param meetingId
128
+ * @returns
129
+ */
130
+ getOrigin(options: GetOriginOptions, meetingId?: string) {
131
+ const defaultClientType: ClientType =
132
+ // @ts-ignore
133
+ this.webex.meetings.config?.metrics?.clientType;
134
+ const defaultSubClientType: SubClientType =
135
+ // @ts-ignore
136
+ this.webex.meetings.config?.metrics?.subClientType;
137
+ // @ts-ignore
138
+ const providedClientVersion: string = this.webex.meetings.config?.metrics?.clientVersion;
139
+ // @ts-ignore
140
+ const defaultSDKClientVersion = `${CLIENT_NAME}/${this.webex.version}`;
141
+
142
+ let versionMetadata: Pick<ClientInfo, 'majorVersion' | 'minorVersion'> = {};
143
+
144
+ // sdk version split doesn't really make sense for now...
145
+ if (providedClientVersion) {
146
+ versionMetadata = extractVersionMetadata(providedClientVersion);
147
+ }
148
+
149
+ if (
150
+ (defaultClientType && defaultSubClientType) ||
151
+ (options.clientType && options.subClientType)
152
+ ) {
153
+ const origin: Event['origin'] = {
154
+ name: 'endpoint',
155
+ networkType: options?.networkType || 'unknown',
156
+ userAgent: userAgentToString({
157
+ // @ts-ignore
158
+ clientName: this.webex.meetings?.config?.metrics?.clientName,
159
+ // @ts-ignore
160
+ webexVersion: this.webex.version,
161
+ }),
162
+ clientInfo: {
163
+ clientType: options?.clientType || defaultClientType,
164
+ clientVersion: providedClientVersion || defaultSDKClientVersion,
165
+ ...versionMetadata,
166
+ localNetworkPrefix:
167
+ // @ts-ignore
168
+ anonymizeIPAddress(this.webex.meetings.geoHintInfo?.clientAddress) || undefined,
169
+ osVersion: getOSVersion() || 'unknown',
170
+ subClientType: options?.subClientType || defaultSubClientType,
171
+ os: getOSNameInternal(),
172
+ browser: getBrowserName(),
173
+ browserVersion: getBrowserVersion(),
174
+ },
175
+ };
176
+
177
+ if (meetingId) {
178
+ // @ts-ignore
179
+ const meeting = this.webex.meetings.meetingCollection.get(meetingId);
180
+ if (meeting?.environment) {
181
+ origin.environment = meeting.environment;
182
+ }
183
+ }
184
+
185
+ if (options?.environment) {
186
+ origin.environment = options.environment;
187
+ }
188
+
189
+ if (options?.newEnvironment) {
190
+ origin.newEnvironment = options.newEnvironment;
191
+ }
192
+
193
+ if (options?.clientLaunchMethod) {
194
+ origin.clientInfo.clientLaunchMethod = options.clientLaunchMethod;
195
+ }
196
+
197
+ return origin;
198
+ }
199
+
200
+ throw new Error("ClientType and SubClientType can't be undefined");
201
+ }
202
+
203
+ /**
204
+ * Gather identifier details for call diagnostic payload.
205
+ * @throws Error if initialization fails.
206
+ * @param options
207
+ */
208
+ getIdentifiers(options: GetIdentifiersOptions) {
209
+ const {meeting, mediaConnections, correlationId} = options;
210
+ const identifiers: Event['event']['identifiers'] = {correlationId: 'unknown'};
211
+
212
+ if (meeting) {
213
+ identifiers.correlationId = meeting.correlationId;
214
+ }
215
+
216
+ if (correlationId) {
217
+ identifiers.correlationId = correlationId;
218
+ }
219
+ // @ts-ignore
220
+ if (this.webex.internal) {
221
+ // @ts-ignore
222
+ const {device} = this.webex.internal;
223
+ identifiers.userId = device.userId;
224
+ identifiers.deviceId = device.url;
225
+ identifiers.orgId = device.orgId;
226
+ // @ts-ignore
227
+ identifiers.locusUrl = this.webex.internal.services.get('locus');
228
+ }
229
+
230
+ if (meeting?.locusInfo?.fullState) {
231
+ identifiers.locusUrl = meeting.locusUrl;
232
+ identifiers.locusId = meeting.locusUrl && meeting.locusUrl.split('/').pop();
233
+ identifiers.locusStartTime =
234
+ meeting.locusInfo.fullState && meeting.locusInfo.fullState.lastActive;
235
+ }
236
+
237
+ if (mediaConnections) {
238
+ identifiers.mediaAgentAlias = mediaConnections?.[0]?.mediaAgentAlias;
239
+ identifiers.mediaAgentGroupId = mediaConnections?.[0]?.mediaAgentGroupId;
240
+ }
241
+
242
+ if (identifiers.correlationId === undefined) {
243
+ throw new Error('Identifiers initialization failed.');
244
+ }
245
+
246
+ return identifiers;
247
+ }
248
+
249
+ /**
250
+ * Create diagnostic event, which can hold client event, feature event or MQE event data.
251
+ * This just initiates the shared properties that are required for all the 3 event categories.
252
+ * @param eventData
253
+ * @param options
254
+ * @returns
255
+ */
256
+ prepareDiagnosticEvent(eventData: Event['event'], options: any) {
257
+ const {meetingId} = options;
258
+ const origin = this.getOrigin(options, meetingId);
259
+
260
+ const event: Event = {
261
+ eventId: uuid.v4(),
262
+ version: 1,
263
+ origin,
264
+ originTime: {
265
+ triggered: new Date().toISOString(),
266
+ // is overridden in prepareRequest batcher
267
+ sent: 'not_defined_yet',
268
+ },
269
+ // @ts-ignore
270
+ senderCountryCode: this.webex.meetings.geoHintInfo?.countryCode,
271
+ event: eventData,
272
+ };
273
+
274
+ // sanitize (remove empty properties, CA requires it)
275
+ // but we don't want to sanitize MQE as most of the times
276
+ // values will be 0, [] etc, and they are required.
277
+ if (eventData.name !== 'client.mediaquality.event') {
278
+ clearEmptyKeysRecursively(event);
279
+ }
280
+
281
+ return event;
282
+ }
283
+
284
+ /**
285
+ * TODO: NOT IMPLEMENTED
286
+ * Submit Feature Event
287
+ * @returns
288
+ */
289
+ public submitFeatureEvent() {
290
+ throw Error('Not implemented');
291
+ }
292
+
293
+ /**
294
+ * Submit Media Quality Event
295
+ * @param args - submit params
296
+ * @param arg.name - event key
297
+ * @param arg.payload - additional payload to be merge with the default payload
298
+ * @param arg.options - options
299
+ */
300
+ submitMQE({
301
+ name,
302
+ payload,
303
+ options,
304
+ }: {
305
+ name: MediaQualityEvent['name'];
306
+ payload: SubmitMQEPayload;
307
+ options: SubmitMQEOptions;
308
+ }) {
309
+ const {meetingId, mediaConnections} = options;
310
+
311
+ // events that will most likely happen in join phase
312
+ if (meetingId) {
313
+ // @ts-ignore
314
+ const meeting = this.webex.meetings.meetingCollection.get(meetingId);
315
+
316
+ if (!meeting) {
317
+ console.warn(
318
+ 'Attempt to send MQE but no meeting was found...',
319
+ `event: ${name}, meetingId: ${meetingId}`
320
+ );
321
+ // @ts-ignore
322
+ this.webex.internal.metrics.submitClientMetrics(CALL_DIAGNOSTIC_EVENT_FAILED_TO_SEND, {
323
+ fields: {
324
+ meetingId,
325
+ name,
326
+ },
327
+ });
328
+
329
+ return;
330
+ }
331
+
332
+ // merge identifiers
333
+ const identifiers = this.getIdentifiers({
334
+ meeting,
335
+ mediaConnections: meeting.mediaConnections || mediaConnections,
336
+ });
337
+
338
+ // create media quality event object
339
+ let clientEventObject: MediaQualityEvent['payload'] = {
340
+ name,
341
+ canProceed: true,
342
+ identifiers,
343
+ eventData: {
344
+ webClientDomain: window.location.hostname,
345
+ },
346
+ intervals: payload.intervals,
347
+ sourceMetadata: {
348
+ applicationSoftwareType: CLIENT_NAME,
349
+ // @ts-ignore
350
+ applicationSoftwareVersion: this.webex.version,
351
+ mediaEngineSoftwareType: getBrowserName() || 'browser',
352
+ mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
353
+ startTime: new Date().toISOString(),
354
+ },
355
+ };
356
+
357
+ // merge any new properties, or override existing ones
358
+ clientEventObject = merge(clientEventObject, payload);
359
+
360
+ // append media quality event data to the call diagnostic event
361
+ const diagnosticEvent = this.prepareDiagnosticEvent(clientEventObject, options);
362
+ this.submitToCallDiagnostics(diagnosticEvent);
363
+ } else {
364
+ throw new Error(
365
+ 'Media quality events cant be sent outside the context of a meeting. Meeting id is required.'
366
+ );
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Return Client Event payload by client error code
372
+ * @param arg - get error arg
373
+ * @param arg.clientErrorCode
374
+ * @param arg.serviceErrorCode
375
+ * @returns
376
+ */
377
+ public getErrorPayloadForClientErrorCode({
378
+ clientErrorCode,
379
+ serviceErrorCode,
380
+ serviceErrorName,
381
+ }: {
382
+ clientErrorCode: number;
383
+ serviceErrorCode: any;
384
+ serviceErrorName?: any;
385
+ }): ClientEventError {
386
+ let error: ClientEventError;
387
+
388
+ if (clientErrorCode) {
389
+ const partialParsedError = CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD[clientErrorCode];
390
+
391
+ if (partialParsedError) {
392
+ error = merge(
393
+ {fatal: true, shownToUser: false, name: 'other', category: 'other'}, // default values
394
+ {errorCode: clientErrorCode},
395
+ serviceErrorName ? {errorData: {errorName: serviceErrorName}} : {},
396
+ {serviceErrorCode},
397
+ partialParsedError
398
+ );
399
+
400
+ return error;
401
+ }
402
+ }
403
+
404
+ return undefined;
405
+ }
406
+
407
+ /**
408
+ * Generate error payload for Client Event
409
+ * @param rawError
410
+ */
411
+ generateClientEventErrorPayload(rawError: any) {
412
+ if (rawError.name) {
413
+ if (isBrowserMediaErrorName(rawError.name)) {
414
+ return this.getErrorPayloadForClientErrorCode({
415
+ serviceErrorCode: undefined,
416
+ clientErrorCode: BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP[rawError.name],
417
+ serviceErrorName: rawError.name,
418
+ });
419
+ }
420
+ }
421
+
422
+ const serviceErrorCode =
423
+ rawError?.error?.body?.errorCode ||
424
+ rawError?.body?.errorCode ||
425
+ rawError?.body?.code ||
426
+ rawError?.body?.reason?.reasonCode;
427
+
428
+ if (serviceErrorCode) {
429
+ const clientErrorCode = SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP[serviceErrorCode];
430
+ if (clientErrorCode) {
431
+ return this.getErrorPayloadForClientErrorCode({clientErrorCode, serviceErrorCode});
432
+ }
433
+
434
+ // by default, if it is locus error, return new locus err
435
+ if (isLocusServiceErrorCode(serviceErrorCode)) {
436
+ return this.getErrorPayloadForClientErrorCode({
437
+ clientErrorCode: NEW_LOCUS_ERROR_CLIENT_CODE,
438
+ serviceErrorCode,
439
+ });
440
+ }
441
+ }
442
+
443
+ if (isMeetingInfoServiceError(rawError)) {
444
+ return this.getErrorPayloadForClientErrorCode({
445
+ clientErrorCode: MEETING_INFO_LOOKUP_ERROR_CLIENT_CODE,
446
+ serviceErrorCode,
447
+ });
448
+ }
449
+
450
+ if (isNetworkError(rawError)) {
451
+ const payload = this.getErrorPayloadForClientErrorCode({
452
+ clientErrorCode: NETWORK_ERROR,
453
+ serviceErrorCode,
454
+ });
455
+ payload.errorDescription = rawError.message;
456
+
457
+ return payload;
458
+ }
459
+
460
+ if (isUnauthorizedError(rawError)) {
461
+ const payload = this.getErrorPayloadForClientErrorCode({
462
+ clientErrorCode: AUTHENTICATION_FAILED_CODE,
463
+ serviceErrorCode,
464
+ });
465
+ payload.errorDescription = rawError.message;
466
+
467
+ return payload;
468
+ }
469
+
470
+ // otherwise return unkown error
471
+ return this.getErrorPayloadForClientErrorCode({
472
+ clientErrorCode: UNKNOWN_ERROR,
473
+ serviceErrorCode: UNKNOWN_ERROR,
474
+ });
475
+ }
476
+
477
+ /**
478
+ * Create client event object for in meeting events
479
+ * @param arg - create args
480
+ * @param arg.event - event key
481
+ * @param arg.options - options
482
+ * @returns object
483
+ */
484
+ private createClientEventObjectInMeeting({
485
+ name,
486
+ options,
487
+ errors,
488
+ }: {
489
+ name: ClientEvent['name'];
490
+ options?: SubmitClientEventOptions;
491
+ errors?: ClientEventPayloadError;
492
+ }) {
493
+ this.logger.log(
494
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
495
+ 'CallDiagnosticMetrics: @createClientEventObjectInMeeting. Creating in meeting event object.',
496
+ `name: ${name}`
497
+ );
498
+ const {meetingId, mediaConnections} = options;
499
+
500
+ // @ts-ignore
501
+ const meeting = this.webex.meetings.meetingCollection.get(meetingId);
502
+
503
+ if (!meeting) {
504
+ console.warn(
505
+ 'Attempt to send client event but no meeting was found...',
506
+ `name: ${name}, meetingId: ${meetingId}`
507
+ );
508
+ // @ts-ignore
509
+ this.webex.internal.metrics.submitClientMetrics(CALL_DIAGNOSTIC_EVENT_FAILED_TO_SEND, {
510
+ fields: {
511
+ meetingId,
512
+ name,
513
+ },
514
+ });
515
+
516
+ return undefined;
517
+ }
518
+
519
+ // grab identifiers
520
+ const identifiers = this.getIdentifiers({
521
+ meeting,
522
+ mediaConnections: meeting?.mediaConnections || mediaConnections,
523
+ });
524
+
525
+ // create client event object
526
+ const clientEventObject: ClientEvent['payload'] = {
527
+ name,
528
+ canProceed: true,
529
+ identifiers,
530
+ errors,
531
+ eventData: {
532
+ webClientDomain: window.location.hostname,
533
+ },
534
+ userType: meeting.getCurUserType(),
535
+ loginType: this.getCurLoginType(),
536
+ isConvergedArchitectureEnabled: this.getIsConvergedArchitectureEnabled({
537
+ meetingId,
538
+ }),
539
+ };
540
+
541
+ if (options?.rawError?.message) {
542
+ // @ts-ignore
543
+ clientEventObject.eventData.rawErrorMessage = options?.rawError?.message;
544
+ }
545
+
546
+ return clientEventObject;
547
+ }
548
+
549
+ /**
550
+ * Create client event object for pre meeting events
551
+ * @param arg - create args
552
+ * @param arg.event - event key
553
+ * @param arg.options - payload
554
+ * @returns object
555
+ */
556
+ private createClientEventObjectPreMeeting({
557
+ name,
558
+ options,
559
+ errors,
560
+ }: {
561
+ name: ClientEvent['name'];
562
+ options?: SubmitClientEventOptions;
563
+ errors?: ClientEventPayloadError;
564
+ }) {
565
+ this.logger.log(
566
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
567
+ 'CallDiagnosticMetrics: @createClientEventObjectPreMeeting. Creating pre meeting event object.',
568
+ `name: ${name}`
569
+ );
570
+ const {correlationId} = options;
571
+
572
+ // grab identifiers
573
+ const identifiers = this.getIdentifiers({
574
+ correlationId,
575
+ });
576
+
577
+ // create client event object
578
+ const clientEventObject: ClientEvent['payload'] = {
579
+ name,
580
+ errors,
581
+ canProceed: true,
582
+ identifiers,
583
+ eventData: {
584
+ webClientDomain: window.location.hostname,
585
+ },
586
+ loginType: this.getCurLoginType(),
587
+ };
588
+
589
+ if (options?.rawError?.message) {
590
+ // @ts-ignore
591
+ clientEventObject.eventData.rawErrorMessage = options?.rawError?.message;
592
+ }
593
+
594
+ return clientEventObject;
595
+ }
596
+
597
+ /**
598
+ * Prepare Client Event CA event.
599
+ * @param arg - submit params
600
+ * @param arg.event - event key
601
+ * @param arg.payload - additional payload to be merged with default payload
602
+ * @param arg.options - payload
603
+ * @returns {any} options to be with fetch
604
+ * @throws
605
+ */
606
+ private prepareClientEvent({
607
+ name,
608
+ payload,
609
+ options,
610
+ }: {
611
+ name: ClientEvent['name'];
612
+ payload?: ClientEventPayload;
613
+ options?: SubmitClientEventOptions;
614
+ }) {
615
+ const {meetingId, correlationId, rawError} = options;
616
+ let clientEventObject: ClientEvent['payload'];
617
+
618
+ // check if we need to generate errors
619
+ const errors: ClientEventPayloadError = [];
620
+
621
+ if (rawError) {
622
+ this.logger.log(
623
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
624
+ 'CallDiagnosticMetrics: @prepareClientEvent. Error detected, attempting to map and attach it to the event...',
625
+ `name: ${name}`,
626
+ `rawError: ${generateCommonErrorMetadata(rawError)}`
627
+ );
628
+
629
+ const generatedError = this.generateClientEventErrorPayload(rawError);
630
+ if (generatedError) {
631
+ errors.push(generatedError);
632
+ }
633
+ this.logger.log(
634
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
635
+ 'CallDiagnosticMetrics: @prepareClientEvent. Generated errors:',
636
+ `generatedError: ${JSON.stringify(generatedError)}`
637
+ );
638
+ }
639
+
640
+ // events that will most likely happen in join phase
641
+ if (meetingId) {
642
+ clientEventObject = this.createClientEventObjectInMeeting({name, options, errors});
643
+ } else if (correlationId) {
644
+ // any pre join events or events that are outside the meeting.
645
+ clientEventObject = this.createClientEventObjectPreMeeting({name, options, errors});
646
+ } else {
647
+ throw new Error('Not implemented');
648
+ }
649
+
650
+ // merge any new properties, or override existing ones
651
+ clientEventObject = merge(clientEventObject, payload);
652
+
653
+ // append client event data to the call diagnostic event
654
+ const diagnosticEvent = this.prepareDiagnosticEvent(clientEventObject, options);
655
+
656
+ return diagnosticEvent;
657
+ }
658
+
659
+ /**
660
+ * Submit Client Event CA event.
661
+ * @param arg - submit params
662
+ * @param arg.event - event key
663
+ * @param arg.payload - additional payload to be merged with default payload
664
+ * @param arg.options - payload
665
+ * @throws
666
+ */
667
+ public submitClientEvent({
668
+ name,
669
+ payload,
670
+ options,
671
+ }: {
672
+ name: ClientEvent['name'];
673
+ payload?: ClientEventPayload;
674
+ options?: SubmitClientEventOptions;
675
+ }) {
676
+ this.logger.log(
677
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
678
+ 'CallDiagnosticMetrics: @submitClientEvent. Submit Client Event CA event.',
679
+ `name: ${name}`,
680
+ `payload: ${JSON.stringify(payload)}`,
681
+ `options: ${JSON.stringify(options)}`
682
+ );
683
+ const diagnosticEvent = this.prepareClientEvent({name, payload, options});
684
+
685
+ if (options?.preLoginId) {
686
+ return this.submitToCallDiagnosticsPreLogin(diagnosticEvent, options?.preLoginId);
687
+ }
688
+
689
+ return this.submitToCallDiagnostics(diagnosticEvent);
690
+ }
691
+
692
+ /**
693
+ * Prepare the event and send the request to metrics-a service.
694
+ * @param event
695
+ * @returns promise
696
+ */
697
+ submitToCallDiagnostics(event: Event): Promise<any> {
698
+ // build metrics-a event type
699
+ const finalEvent = {
700
+ eventPayload: event,
701
+ type: ['diagnostic-event'],
702
+ };
703
+
704
+ this.logger.log(
705
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
706
+ 'CallDiagnosticMetrics: @submitToCallDiagnostics. Preparing to send the request',
707
+ `finalEvent: ${JSON.stringify(finalEvent)}`
708
+ );
709
+
710
+ return this.callDiagnosticEventsBatcher.request(finalEvent);
711
+ }
712
+
713
+ /**
714
+ * Pre login events are not batched. We make the request directly.
715
+ * @param event
716
+ * @param preLoginId
717
+ * @returns
718
+ */
719
+ public submitToCallDiagnosticsPreLogin = (event: Event, preLoginId?: string): Promise<any> => {
720
+ // build metrics-a event type
721
+ // @ts-ignore
722
+ const diagnosticEvent = prepareDiagnosticMetricItem(this.webex, {
723
+ eventPayload: event,
724
+ type: ['diagnostic-event'],
725
+ });
726
+
727
+ // append sent timestamp
728
+ diagnosticEvent.eventPayload.originTime.sent = new Date().toISOString();
729
+
730
+ this.logger.log(
731
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
732
+ `CallDiagnosticMetrics: @submitToCallDiagnosticsPreLogin. Sending the request:`,
733
+ `diagnosticEvent: ${JSON.stringify(diagnosticEvent)}`
734
+ );
735
+
736
+ // @ts-ignore
737
+ return this.webex.internal.newMetrics.postPreLoginMetric(diagnosticEvent, preLoginId);
738
+ };
739
+
740
+ /**
741
+ * Builds a request options object to later be passed to fetch().
742
+ * @param arg - submit params
743
+ * @param arg.event - event key
744
+ * @param arg.payload - additional payload to be merged with default payload
745
+ * @param arg.options - client event options
746
+ * @returns {Promise<any>}
747
+ * @throws
748
+ */
749
+ public async buildClientEventFetchRequestOptions({
750
+ name,
751
+ payload,
752
+ options,
753
+ }: {
754
+ name: ClientEvent['name'];
755
+ payload?: ClientEventPayload;
756
+ options?: SubmitClientEventOptions;
757
+ }): Promise<any> {
758
+ this.logger.log(
759
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
760
+ 'CallDiagnosticMetrics: @buildClientEventFetchRequestOptions. Building request options object for fetch()...',
761
+ `name: ${name}`,
762
+ `payload: ${JSON.stringify(payload)}`,
763
+ `options: ${JSON.stringify(options)}`
764
+ );
765
+
766
+ const clientEvent = this.prepareClientEvent({name, payload, options});
767
+
768
+ // build metrics-a event type
769
+ // @ts-ignore
770
+ const diagnosticEvent = prepareDiagnosticMetricItem(this.webex, {
771
+ eventPayload: clientEvent,
772
+ type: ['diagnostic-event'],
773
+ });
774
+
775
+ const request = {
776
+ method: 'POST',
777
+ service: 'metrics',
778
+ resource: 'clientmetrics',
779
+ body: {
780
+ metrics: [diagnosticEvent],
781
+ },
782
+ headers: {},
783
+ };
784
+
785
+ if (options.preLoginId) {
786
+ request.headers = {
787
+ authorization: false,
788
+ 'x-prelogin-userid': options.preLoginId,
789
+ };
790
+ request.resource = 'clientmetrics-prelogin';
791
+ }
792
+
793
+ // @ts-ignore
794
+ return this.webex.prepareFetchOptions(request);
795
+ }
796
+
797
+ /**
798
+ * Returns true if the specified serviceErrorCode maps to an expected error.
799
+ * @param {number} serviceErrorCode the service error code
800
+ * @returns {boolean}
801
+ */
802
+ public isServiceErrorExpected(serviceErrorCode: number): boolean {
803
+ const clientErrorCode = SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP[serviceErrorCode];
804
+ const clientErrorPayload = CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD[clientErrorCode];
805
+
806
+ return clientErrorPayload?.category === 'expected';
807
+ }
808
+ }