@webex/internal-plugin-metrics 3.8.1 → 3.9.0

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 (35) hide show
  1. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +92 -14
  2. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
  3. package/dist/call-diagnostic/call-diagnostic-metrics.js +348 -48
  4. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
  5. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +21 -0
  6. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
  7. package/dist/call-diagnostic/config.js +3 -1
  8. package/dist/call-diagnostic/config.js.map +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/metrics.js +1 -1
  11. package/dist/metrics.types.js.map +1 -1
  12. package/dist/new-metrics.js +43 -1
  13. package/dist/new-metrics.js.map +1 -1
  14. package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +23 -1
  15. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +176 -10
  16. package/dist/types/call-diagnostic/config.d.ts +2 -0
  17. package/dist/types/index.d.ts +2 -2
  18. package/dist/types/metrics.types.d.ts +18 -7
  19. package/dist/types/new-metrics.d.ts +19 -2
  20. package/package.json +11 -12
  21. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +104 -14
  22. package/src/call-diagnostic/call-diagnostic-metrics.ts +363 -25
  23. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +20 -0
  24. package/src/call-diagnostic/config.ts +3 -0
  25. package/src/index.ts +2 -0
  26. package/src/metrics.types.ts +25 -6
  27. package/src/new-metrics.ts +52 -1
  28. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +20 -1
  29. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +255 -0
  30. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +829 -39
  31. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +6 -0
  32. package/test/unit/spec/new-metrics.ts +67 -2
  33. package/test/unit/spec/prelogin-metrics-batcher.ts +72 -3
  34. package/dist/call-diagnostic-events-batcher.js +0 -60
  35. package/dist/call-diagnostic-events-batcher.js.map +0 -1
@@ -148,15 +148,27 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
148
148
  * @param b end
149
149
  * @returns latency
150
150
  */
151
- public getDiffBetweenTimestamps(a: MetricEventNames, b: MetricEventNames) {
151
+ public getDiffBetweenTimestamps(
152
+ a: MetricEventNames,
153
+ b: MetricEventNames,
154
+ clampValues?: {minimum?: number; maximum?: number}
155
+ ) {
152
156
  const start = this.latencyTimestamps.get(a);
153
157
  const end = this.latencyTimestamps.get(b);
154
158
 
155
- if (typeof start === 'number' && typeof end === 'number') {
156
- return end - start;
159
+ if (typeof start !== 'number' || typeof end !== 'number') {
160
+ return undefined;
157
161
  }
158
162
 
159
- return undefined;
163
+ const diff = end - start;
164
+
165
+ if (!clampValues) {
166
+ return diff;
167
+ }
168
+
169
+ const {minimum = 0, maximum} = clampValues;
170
+
171
+ return Math.min(maximum ?? Infinity, Math.max(diff, minimum));
160
172
  }
161
173
 
162
174
  /**
@@ -172,7 +184,8 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
172
184
  public getMeetingInfoReqResp() {
173
185
  return this.getDiffBetweenTimestamps(
174
186
  'internal.client.meetinginfo.request',
175
- 'internal.client.meetinginfo.response'
187
+ 'internal.client.meetinginfo.response',
188
+ {maximum: 1200000}
176
189
  );
177
190
  }
178
191
 
@@ -215,7 +228,8 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
215
228
  public getCallInitJoinReq() {
216
229
  return this.getDiffBetweenTimestamps(
217
230
  'internal.client.interstitial-window.click.joinbutton',
218
- 'client.locus.join.request'
231
+ 'client.locus.join.request',
232
+ {maximum: 1200000}
219
233
  );
220
234
  }
221
235
 
@@ -224,7 +238,11 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
224
238
  * @returns - latency
225
239
  */
226
240
  public getJoinReqResp() {
227
- return this.getDiffBetweenTimestamps('client.locus.join.request', 'client.locus.join.response');
241
+ return this.getDiffBetweenTimestamps(
242
+ 'client.locus.join.request',
243
+ 'client.locus.join.response',
244
+ {maximum: 1200000}
245
+ );
228
246
  }
229
247
 
230
248
  /**
@@ -245,7 +263,8 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
245
263
  public getLocalSDPGenRemoteSDPRecv() {
246
264
  return this.getDiffBetweenTimestamps(
247
265
  'client.media-engine.local-sdp-generated',
248
- 'client.media-engine.remote-sdp-received'
266
+ 'client.media-engine.remote-sdp-received',
267
+ {maximum: 1200000}
249
268
  );
250
269
  }
251
270
 
@@ -254,7 +273,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
254
273
  * @returns - latency
255
274
  */
256
275
  public getICESetupTime() {
257
- return this.getDiffBetweenTimestamps('client.ice.start', 'client.ice.end');
276
+ return this.getDiffBetweenTimestamps('client.ice.start', 'client.ice.end', {maximum: 1200000});
258
277
  }
259
278
 
260
279
  /**
@@ -336,6 +355,30 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
336
355
  return undefined;
337
356
  }
338
357
 
358
+ /**
359
+ * Click To Interstitial With User Delay
360
+ * @returns - latency
361
+ */
362
+ public getClickToInterstitialWithUserDelay() {
363
+ // for normal join (where green join button exists before interstitial, i.e reminder, space list etc)
364
+ if (this.latencyTimestamps.get('internal.client.meeting.click.joinbutton')) {
365
+ return this.getDiffBetweenTimestamps(
366
+ 'internal.client.meeting.click.joinbutton',
367
+ 'internal.client.meeting.interstitial-window.showed'
368
+ );
369
+ }
370
+
371
+ const clickToInterstitialWithUserDelayLatency = this.precomputedLatencies.get(
372
+ 'internal.click.to.interstitial.with.user.delay'
373
+ );
374
+
375
+ if (typeof clickToInterstitialWithUserDelayLatency === 'number') {
376
+ return clickToInterstitialWithUserDelayLatency;
377
+ }
378
+
379
+ return undefined;
380
+ }
381
+
339
382
  /**
340
383
  * Interstitial To Join Ok
341
384
  * @returns - latency
@@ -354,7 +397,8 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
354
397
  public getCallInitMediaEngineReady() {
355
398
  return this.getDiffBetweenTimestamps(
356
399
  'internal.client.interstitial-window.click.joinbutton',
357
- 'client.media-engine.ready'
400
+ 'client.media-engine.ready',
401
+ {maximum: 1200000}
358
402
  );
359
403
  }
360
404
 
@@ -374,7 +418,9 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
374
418
  const lobbyTime = typeof lobbyTimeLatency === 'number' ? lobbyTimeLatency : 0;
375
419
 
376
420
  if (interstitialJoinClickTimestamp && connectedMedia) {
377
- return connectedMedia - interstitialJoinClickTimestamp - lobbyTime;
421
+ const interstitialToMediaOKJmt = connectedMedia - interstitialJoinClickTimestamp - lobbyTime;
422
+
423
+ return Math.max(0, interstitialToMediaOKJmt);
378
424
  }
379
425
 
380
426
  return undefined;
@@ -395,6 +441,24 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
395
441
  return undefined;
396
442
  }
397
443
 
444
+ /**
445
+ * Total JMT With User Delay
446
+ * @returns - latency
447
+ */
448
+ public getTotalJMTWithUserDelay() {
449
+ const clickToInterstitialWithUserDelay = this.getClickToInterstitialWithUserDelay();
450
+ const interstitialToJoinOk = this.getInterstitialToJoinOK();
451
+
452
+ if (
453
+ typeof clickToInterstitialWithUserDelay === 'number' &&
454
+ typeof interstitialToJoinOk === 'number'
455
+ ) {
456
+ return clickToInterstitialWithUserDelay + interstitialToJoinOk;
457
+ }
458
+
459
+ return undefined;
460
+ }
461
+
398
462
  /**
399
463
  * Join Conf JMT
400
464
  * @returns - latency
@@ -421,12 +485,28 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
421
485
  const lobbyTime = this.getStayLobbyTime();
422
486
 
423
487
  if (clickToInterstitial && interstitialToJoinOk && joinConfJMT) {
424
- const totalMediaJMT = clickToInterstitial + interstitialToJoinOk + joinConfJMT;
488
+ const totalMediaJMT = Math.max(0, clickToInterstitial + interstitialToJoinOk + joinConfJMT);
425
489
  if (this.getMeeting()?.allowMediaInLobby) {
426
490
  return totalMediaJMT;
427
491
  }
428
492
 
429
- return totalMediaJMT - lobbyTime;
493
+ return Math.max(0, totalMediaJMT - lobbyTime);
494
+ }
495
+
496
+ return undefined;
497
+ }
498
+
499
+ /**
500
+ * Total Media JMT With User Delay
501
+ * @returns - latency
502
+ */
503
+ public getTotalMediaJMTWithUserDelay() {
504
+ const clickToInterstitialWithUserDelay = this.getClickToInterstitialWithUserDelay();
505
+ const interstitialToJoinOk = this.getInterstitialToJoinOK();
506
+ const joinConfJMT = this.getJoinConfJMT();
507
+
508
+ if (clickToInterstitialWithUserDelay && interstitialToJoinOk && joinConfJMT) {
509
+ return Math.max(0, clickToInterstitialWithUserDelay + interstitialToJoinOk + joinConfJMT);
430
510
  }
431
511
 
432
512
  return undefined;
@@ -441,7 +521,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
441
521
  const joinConfJMT = this.getJoinConfJMT();
442
522
 
443
523
  if (typeof interstitialToJoinOk === 'number' && typeof joinConfJMT === 'number') {
444
- return interstitialToJoinOk - joinConfJMT;
524
+ return Math.max(0, interstitialToJoinOk - joinConfJMT);
445
525
  }
446
526
 
447
527
  return undefined;
@@ -486,6 +566,16 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
486
566
  return this.getDiffBetweenTimestamps('client.locus.join.response', 'client.media.tx.start');
487
567
  }
488
568
 
569
+ /**
570
+ * Time from share initiation to share stop (ms).
571
+ */
572
+ public getShareDuration() {
573
+ return this.getDiffBetweenTimestamps(
574
+ 'internal.client.share.initiated',
575
+ 'internal.client.share.stopped'
576
+ );
577
+ }
578
+
489
579
  /**
490
580
  * Total latency for all exchange ci token.
491
581
  */
@@ -41,6 +41,9 @@ import {
41
41
  ClientSubServiceType,
42
42
  BrowserLaunchMethodType,
43
43
  DelayedClientEvent,
44
+ DelayedClientFeatureEvent,
45
+ FeatureEvent,
46
+ ClientFeatureEventPayload,
44
47
  } from '../metrics.types';
45
48
  import CallDiagnosticEventsBatcher from './call-diagnostic-metrics-batcher';
46
49
  import PreLoginMetricsBatcher from '../prelogin-metrics-batcher';
@@ -58,6 +61,8 @@ import {
58
61
  AUTHENTICATION_FAILED_CODE,
59
62
  WEBEX_SUB_SERVICE_TYPES,
60
63
  SDP_OFFER_CREATION_ERROR_MAP,
64
+ CALL_FEATURE_LOG_IDENTIFIER,
65
+ CALL_FEATURE_EVENT_FAILED_TO_SEND,
61
66
  } from './config';
62
67
 
63
68
  const {getOSVersion, getBrowserName, getBrowserVersion} = BrowserDetection();
@@ -97,7 +102,11 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
97
102
  private hasLoggedBrowserSerial: boolean;
98
103
  private device: any;
99
104
  private delayedClientEvents: DelayedClientEvent[] = [];
105
+ private delayedClientFeatureEvents: DelayedClientFeatureEvent[] = [];
100
106
  private eventErrorCache: WeakMap<any, any> = new WeakMap();
107
+ private isMercuryConnected = false;
108
+ private eventLimitTracker: Map<string, number> = new Map();
109
+ private eventLimitWarningsLogged: Set<string> = new Set();
101
110
 
102
111
  // the default validator before piping an event to the batcher
103
112
  // this function can be overridden by the user
@@ -150,6 +159,16 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
150
159
  return undefined;
151
160
  }
152
161
 
162
+ /**
163
+ * Sets mercury connected status for event data object in CA events
164
+ * @public
165
+ * @param status - boolean value indicating mercury connection status
166
+ * @return {void}
167
+ */
168
+ public setMercuryConnectedStatus(status: boolean): void {
169
+ this.isMercuryConnected = status;
170
+ }
171
+
153
172
  /**
154
173
  * Returns meeting's subServiceType
155
174
  * @param meeting
@@ -416,12 +435,97 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
416
435
  }
417
436
 
418
437
  /**
419
- * TODO: NOT IMPLEMENTED
438
+ * Create feature event
439
+ * @param name
440
+ * @param payload
441
+ * @param options
442
+ * @returns
443
+ */
444
+ private prepareClientFeatureEvent({
445
+ name,
446
+ payload,
447
+ options,
448
+ }: {
449
+ name: FeatureEvent['name'];
450
+ payload?: ClientFeatureEventPayload;
451
+ options?: SubmitClientEventOptions;
452
+ }) {
453
+ const {meetingId, correlationId} = options;
454
+ let featureEventObject: FeatureEvent['payload'];
455
+
456
+ // events that will most likely happen in join phase
457
+ if (meetingId) {
458
+ featureEventObject = this.createFeatureEventObjectInMeeting({name, options});
459
+ } else {
460
+ throw new Error('Not implemented');
461
+ }
462
+
463
+ // merge any new properties, or override existing ones
464
+ featureEventObject = merge(featureEventObject, payload);
465
+
466
+ // append client event data to the call diagnostic event
467
+ const featureEvent = this.prepareDiagnosticEvent(featureEventObject, options);
468
+
469
+ return featureEvent;
470
+ }
471
+
472
+ /**
420
473
  * Submit Feature Event
474
+ * submit to business_ucf
421
475
  * @returns
422
476
  */
423
- public submitFeatureEvent() {
424
- throw Error('Not implemented');
477
+ public submitFeatureEvent({
478
+ name,
479
+ payload,
480
+ options,
481
+ delaySubmitEvent,
482
+ }: {
483
+ name: FeatureEvent['name'];
484
+ payload?: ClientFeatureEventPayload;
485
+ options?: SubmitClientEventOptions;
486
+ delaySubmitEvent?: boolean;
487
+ }) {
488
+ if (delaySubmitEvent) {
489
+ // Preserve the time when the event was triggered if delaying the submission to Call Features
490
+ const delayedOptions = {
491
+ ...options,
492
+ triggeredTime: new Date().toISOString(),
493
+ };
494
+
495
+ this.delayedClientFeatureEvents.push({
496
+ name,
497
+ payload,
498
+ options: delayedOptions,
499
+ });
500
+
501
+ return Promise.resolve();
502
+ }
503
+
504
+ this.logger.log(
505
+ CALL_FEATURE_LOG_IDENTIFIER,
506
+ 'CallFeatureMetrics: @submitFeatureEvent. Submit Client Feature Event CA event.',
507
+ `name: ${name}`
508
+ );
509
+ const featureEvent = this.prepareClientFeatureEvent({name, payload, options});
510
+
511
+ this.validator({type: 'ce', event: featureEvent});
512
+
513
+ return this.submitToCallFeatures(featureEvent);
514
+ }
515
+
516
+ /**
517
+ * Submit Feature Event
518
+ * type is business
519
+ * @param event
520
+ */
521
+ submitToCallFeatures(event: Event): Promise<any> {
522
+ // build metrics-a event type
523
+ const finalEvent = {
524
+ eventPayload: event,
525
+ type: ['business'],
526
+ };
527
+
528
+ return this.callDiagnosticEventsBatcher.request(finalEvent);
425
529
  }
426
530
 
427
531
  /**
@@ -563,6 +667,144 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
563
667
  this.eventErrorCache = new WeakMap();
564
668
  }
565
669
 
670
+ /**
671
+ * Checks if an event should be limited based on criteria defined in the event dictionary.
672
+ * Returns true if the event should be sent, false if it has reached its limit.
673
+ * @param event - The diagnostic event object
674
+ * @returns boolean indicating whether the event should be sent
675
+ */
676
+ private shouldSendEvent({event}: Event): boolean {
677
+ const eventName = event?.name as string;
678
+ const correlationId = event?.identifiers?.correlationId;
679
+
680
+ if (!correlationId || correlationId === 'unknown') {
681
+ return true;
682
+ }
683
+
684
+ const limitKeyPrefix = `${eventName}:${correlationId}`;
685
+
686
+ switch (eventName) {
687
+ case 'client.media.render.start':
688
+ case 'client.media.render.stop':
689
+ case 'client.media.rx.start':
690
+ case 'client.media.rx.stop':
691
+ case 'client.media.tx.start':
692
+ case 'client.media.tx.stop': {
693
+ // Send only once per mediaType-correlationId pair (or mediaType-correlationId-shareInstanceId for share/share_audio)
694
+ const mediaType = event?.mediaType;
695
+ if (mediaType) {
696
+ if (mediaType === 'share' || mediaType === 'share_audio') {
697
+ const shareInstanceId = event?.shareInstanceId;
698
+ if (shareInstanceId) {
699
+ const limitKey = `${limitKeyPrefix}:${mediaType}:${shareInstanceId}`;
700
+
701
+ return this.checkAndIncrementEventCount(
702
+ limitKey,
703
+ 1,
704
+ `${eventName} for ${mediaType} instance ${shareInstanceId}`
705
+ );
706
+ }
707
+ } else {
708
+ const limitKey = `${limitKeyPrefix}:${mediaType}`;
709
+
710
+ return this.checkAndIncrementEventCount(
711
+ limitKey,
712
+ 1,
713
+ `${eventName} for mediaType ${mediaType}`
714
+ );
715
+ }
716
+ }
717
+ break;
718
+ }
719
+
720
+ case 'client.roap-message.received':
721
+ case 'client.roap-message.sent': {
722
+ // Send only once per correlationId and roap.messageType/roap.type
723
+ const roapMessageType = event?.roap?.messageType || event?.roap?.type;
724
+ if (roapMessageType) {
725
+ const limitKey = `${limitKeyPrefix}:${roapMessageType}`;
726
+
727
+ return this.checkAndIncrementEventCount(
728
+ limitKey,
729
+ 1,
730
+ `${eventName} for ROAP type ${roapMessageType}`
731
+ );
732
+ }
733
+ break;
734
+ }
735
+
736
+ default:
737
+ return true;
738
+ }
739
+
740
+ return true;
741
+ }
742
+
743
+ /**
744
+ * Checks the current count for a limit key and increments if under limit.
745
+ * @param limitKey - The unique key for this limit combination
746
+ * @param maxCount - Maximum allowed count
747
+ * @param eventDescription - Description for logging
748
+ * @returns true if under limit and incremented, false if at/over limit
749
+ */
750
+ private checkAndIncrementEventCount(
751
+ limitKey: string,
752
+ maxCount: number,
753
+ eventDescription: string
754
+ ): boolean {
755
+ const currentCount = this.eventLimitTracker.get(limitKey) || 0;
756
+
757
+ if (currentCount >= maxCount) {
758
+ // Log warning only once per limit key
759
+ if (!this.eventLimitWarningsLogged.has(limitKey)) {
760
+ this.logger.log(
761
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
762
+ `CallDiagnosticMetrics: Event limit reached for ${eventDescription}. ` +
763
+ `Max count ${maxCount} exceeded. Event will not be sent.`,
764
+ `limitKey: ${limitKey}`
765
+ );
766
+ this.eventLimitWarningsLogged.add(limitKey);
767
+ }
768
+
769
+ return false;
770
+ }
771
+
772
+ // Increment count and allow event
773
+ this.eventLimitTracker.set(limitKey, currentCount + 1);
774
+
775
+ return true;
776
+ }
777
+
778
+ /**
779
+ * Clears event limit tracking
780
+ */
781
+ public clearEventLimits(): void {
782
+ this.eventLimitTracker.clear();
783
+ this.eventLimitWarningsLogged.clear();
784
+ }
785
+
786
+ /**
787
+ * Clears event limit tracking for a specific correlationId only.
788
+ * Keeps limits for other meetings intact.
789
+ */
790
+ public clearEventLimitsForCorrelationId(correlationId: string): void {
791
+ if (!correlationId) {
792
+ return;
793
+ }
794
+ // Keys are formatted as "eventName:correlationId:..." across all limiters.
795
+ const hasCorrIdAtSecondToken = (key: string) => key.split(':')[1] === correlationId;
796
+ for (const key of Array.from(this.eventLimitTracker.keys())) {
797
+ if (hasCorrIdAtSecondToken(key)) {
798
+ this.eventLimitTracker.delete(key);
799
+ }
800
+ }
801
+ for (const key of Array.from(this.eventLimitWarningsLogged.values())) {
802
+ if (hasCorrIdAtSecondToken(key)) {
803
+ this.eventLimitWarningsLogged.delete(key);
804
+ }
805
+ }
806
+ }
807
+
566
808
  /**
567
809
  * Generate error payload for Client Event
568
810
  * @param rawError
@@ -680,20 +922,20 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
680
922
  }
681
923
 
682
924
  /**
683
- * Create client event object for in meeting events
684
- * @param arg - create args
685
- * @param arg.event - event key
686
- * @param arg.options - options
925
+ * Create common object for in meeting events
926
+ * @param name
927
+ * @param options
928
+ * @param eventType - 'client' | 'feature'
687
929
  * @returns object
688
930
  */
689
- private createClientEventObjectInMeeting({
931
+ private createCommonEventObjectInMeeting({
690
932
  name,
691
933
  options,
692
- errors,
934
+ eventType = 'client',
693
935
  }: {
694
- name: ClientEvent['name'];
936
+ name: string;
695
937
  options?: SubmitClientEventOptions;
696
- errors?: ClientEventPayloadError;
938
+ eventType?: 'client' | 'feature';
697
939
  }) {
698
940
  const {
699
941
  meetingId,
@@ -708,16 +950,21 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
708
950
 
709
951
  if (!meeting) {
710
952
  console.warn(
711
- 'Attempt to send client event but no meeting was found...',
953
+ 'Attempt to send common event but no meeting was found...',
712
954
  `name: ${name}, meetingId: ${meetingId}`
713
955
  );
714
956
  // @ts-ignore
715
- this.webex.internal.metrics.submitClientMetrics(CALL_DIAGNOSTIC_EVENT_FAILED_TO_SEND, {
716
- fields: {
717
- meetingId,
718
- name,
719
- },
720
- });
957
+ this.webex.internal.metrics.submitClientMetrics(
958
+ eventType === 'feature'
959
+ ? CALL_FEATURE_EVENT_FAILED_TO_SEND
960
+ : CALL_DIAGNOSTIC_EVENT_FAILED_TO_SEND,
961
+ {
962
+ fields: {
963
+ meetingId,
964
+ name,
965
+ },
966
+ }
967
+ );
721
968
 
722
969
  return undefined;
723
970
  }
@@ -731,12 +978,11 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
731
978
  sessionCorrelationId,
732
979
  });
733
980
 
734
- // create client event object
735
- const clientEventObject: ClientEvent['payload'] = {
981
+ // create common event object structur
982
+ const commonEventObject = {
736
983
  name,
737
984
  canProceed: true,
738
985
  identifiers,
739
- errors,
740
986
  eventData: {
741
987
  webClientDomain: window.location.hostname,
742
988
  },
@@ -757,18 +1003,80 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
757
1003
 
758
1004
  const joinFlowVersion = options.joinFlowVersion ?? meeting.callStateForMetrics?.joinFlowVersion;
759
1005
  if (joinFlowVersion) {
760
- clientEventObject.joinFlowVersion = joinFlowVersion;
1006
+ // @ts-ignore
1007
+ commonEventObject.joinFlowVersion = joinFlowVersion;
761
1008
  }
762
1009
  const meetingJoinedTime = meeting.isoLocalClientMeetingJoinTime;
763
1010
  if (meetingJoinedTime) {
764
- clientEventObject.meetingJoinedTime = meetingJoinedTime;
1011
+ // @ts-ignore
1012
+ commonEventObject.meetingJoinedTime = meetingJoinedTime;
765
1013
  }
766
1014
 
767
1015
  if (options.meetingJoinPhase) {
768
- clientEventObject.meetingJoinPhase = options.meetingJoinPhase;
1016
+ // @ts-ignore
1017
+ commonEventObject.meetingJoinPhase = options.meetingJoinPhase;
769
1018
  }
770
1019
 
771
- return clientEventObject;
1020
+ return commonEventObject;
1021
+ }
1022
+
1023
+ /**
1024
+ * Create client event object for in meeting events
1025
+ * @param arg - create args
1026
+ * @param arg.event - event key
1027
+ * @param arg.options - options
1028
+ * @returns object
1029
+ */
1030
+ private createClientEventObjectInMeeting({
1031
+ name,
1032
+ options,
1033
+ errors,
1034
+ }: {
1035
+ name: ClientEvent['name'];
1036
+ options?: SubmitClientEventOptions;
1037
+ errors?: ClientEventPayloadError;
1038
+ }) {
1039
+ const commonObject = this.createCommonEventObjectInMeeting({
1040
+ name,
1041
+ options,
1042
+ eventType: 'client',
1043
+ });
1044
+ if (!commonObject) return undefined;
1045
+
1046
+ return {
1047
+ ...commonObject,
1048
+ errors,
1049
+ eventData: {
1050
+ ...commonObject.eventData,
1051
+ isMercuryConnected: this.isMercuryConnected,
1052
+ },
1053
+ } as ClientEvent['payload'];
1054
+ }
1055
+
1056
+ /**
1057
+ * Create feature event object for in meeting function event
1058
+ * @param name
1059
+ * @param options
1060
+ * @returns object
1061
+ */
1062
+ private createFeatureEventObjectInMeeting({
1063
+ name,
1064
+ options,
1065
+ }: {
1066
+ name: FeatureEvent['name'];
1067
+ options?: SubmitClientEventOptions;
1068
+ }) {
1069
+ const commonObject = this.createCommonEventObjectInMeeting({
1070
+ name,
1071
+ options,
1072
+ eventType: 'feature',
1073
+ });
1074
+ if (!commonObject) return undefined;
1075
+
1076
+ return {
1077
+ ...commonObject,
1078
+ key: 'UcfFeatureUsage',
1079
+ } as FeatureEvent['payload'];
772
1080
  }
773
1081
 
774
1082
  /**
@@ -807,6 +1115,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
807
1115
  identifiers,
808
1116
  eventData: {
809
1117
  webClientDomain: window.location.hostname,
1118
+ isMercuryConnected: this.isMercuryConnected,
810
1119
  },
811
1120
  loginType: this.getCurLoginType(),
812
1121
  // @ts-ignore
@@ -930,6 +1239,10 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
930
1239
  );
931
1240
  const diagnosticEvent = this.prepareClientEvent({name, payload, options});
932
1241
 
1242
+ if (!this.shouldSendEvent(diagnosticEvent)) {
1243
+ return Promise.resolve();
1244
+ }
1245
+
933
1246
  if (options?.preLoginId) {
934
1247
  return this.submitToCallDiagnosticsPreLogin(diagnosticEvent, options?.preLoginId);
935
1248
  }
@@ -964,6 +1277,31 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
964
1277
  return Promise.all(promises);
965
1278
  }
966
1279
 
1280
+ /**
1281
+ * Submit Delayed feature Event CA events. Clears submitDelayedClientFeatureEvents array after submission.
1282
+ */
1283
+ public submitDelayedClientFeatureEvents(overrides?: Partial<DelayedClientEvent['options']>) {
1284
+ this.logger.log(
1285
+ CALL_FEATURE_LOG_IDENTIFIER,
1286
+ 'CallDiagnosticMetrics: @submitDelayedClientFeatureEvents. Submitting delayed feature events.'
1287
+ );
1288
+
1289
+ if (this.delayedClientFeatureEvents.length === 0) {
1290
+ return Promise.resolve();
1291
+ }
1292
+
1293
+ const promises = this.delayedClientFeatureEvents.map((delayedSubmitClientEventParams) => {
1294
+ const {name, payload, options} = delayedSubmitClientEventParams;
1295
+ const optionsWithOverrides: DelayedClientEvent['options'] = {...options, ...overrides};
1296
+
1297
+ return this.submitFeatureEvent({name, payload, options: optionsWithOverrides});
1298
+ });
1299
+
1300
+ this.delayedClientFeatureEvents = [];
1301
+
1302
+ return Promise.all(promises);
1303
+ }
1304
+
967
1305
  /**
968
1306
  * Prepare the event and send the request to metrics-a service.
969
1307
  * @param event