@webex/internal-plugin-metrics 3.7.0-next.9 → 3.7.0-wxcc.1

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.
@@ -151,7 +151,8 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
151
151
  public getDiffBetweenTimestamps(a: MetricEventNames, b: MetricEventNames) {
152
152
  const start = this.latencyTimestamps.get(a);
153
153
  const end = this.latencyTimestamps.get(b);
154
- if (start && end) {
154
+
155
+ if (typeof start === 'number' && typeof end === 'number') {
155
156
  return end - start;
156
157
  }
157
158
 
@@ -193,7 +194,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
193
194
  public getU2CTime() {
194
195
  const u2cLatency = this.precomputedLatencies.get('internal.get.u2c.time');
195
196
 
196
- return u2cLatency ? Math.floor(u2cLatency) : undefined;
197
+ return typeof u2cLatency === 'number' ? Math.floor(u2cLatency) : undefined;
197
198
  }
198
199
 
199
200
  /**
@@ -296,7 +297,9 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
296
297
  * @returns - latency
297
298
  */
298
299
  public getPageJMT() {
299
- return this.precomputedLatencies.get('internal.client.pageJMT') || undefined;
300
+ const latency = this.precomputedLatencies.get('internal.client.pageJMT');
301
+
302
+ return typeof latency === 'number' ? latency : undefined;
300
303
  }
301
304
 
302
305
  /**
@@ -304,7 +307,9 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
304
307
  * @returns - latency
305
308
  */
306
309
  public getDownloadTimeJMT() {
307
- return this.precomputedLatencies.get('internal.download.time') || undefined;
310
+ const latency = this.precomputedLatencies.get('internal.download.time');
311
+
312
+ return typeof latency === 'number' ? latency : undefined;
308
313
  }
309
314
 
310
315
  /**
@@ -320,8 +325,15 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
320
325
  );
321
326
  }
322
327
 
323
- // for cross launch and guest flows
324
- return this.precomputedLatencies.get('internal.click.to.interstitial') || undefined;
328
+ const clickToInterstitialLatency = this.precomputedLatencies.get(
329
+ 'internal.click.to.interstitial'
330
+ );
331
+
332
+ if (typeof clickToInterstitialLatency === 'number') {
333
+ return clickToInterstitialLatency;
334
+ }
335
+
336
+ return undefined;
325
337
  }
326
338
 
327
339
  /**
@@ -358,7 +370,8 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
358
370
  // get the first timestamp
359
371
  const connectedMedia = this.latencyTimestamps.get('client.ice.end');
360
372
 
361
- const lobbyTime = this.getStayLobbyTime() || 0;
373
+ const lobbyTimeLatency = this.getStayLobbyTime();
374
+ const lobbyTime = typeof lobbyTimeLatency === 'number' ? lobbyTimeLatency : 0;
362
375
 
363
376
  if (interstitialJoinClickTimestamp && connectedMedia) {
364
377
  return connectedMedia - interstitialJoinClickTimestamp - lobbyTime;
@@ -375,7 +388,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
375
388
  const clickToInterstitial = this.getClickToInterstitial();
376
389
  const interstitialToJoinOk = this.getInterstitialToJoinOK();
377
390
 
378
- if (clickToInterstitial && interstitialToJoinOk) {
391
+ if (typeof clickToInterstitial === 'number' && typeof interstitialToJoinOk === 'number') {
379
392
  return clickToInterstitial + interstitialToJoinOk;
380
393
  }
381
394
 
@@ -427,7 +440,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
427
440
  const interstitialToJoinOk = this.getInterstitialToJoinOK();
428
441
  const joinConfJMT = this.getJoinConfJMT();
429
442
 
430
- if (interstitialToJoinOk && joinConfJMT) {
443
+ if (typeof interstitialToJoinOk === 'number' && typeof joinConfJMT === 'number') {
431
444
  return interstitialToJoinOk - joinConfJMT;
432
445
  }
433
446
 
@@ -454,7 +467,9 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
454
467
  public getReachabilityClustersReqResp() {
455
468
  const reachablityClusterReqResp = this.precomputedLatencies.get('internal.get.cluster.time');
456
469
 
457
- return reachablityClusterReqResp ? Math.floor(reachablityClusterReqResp) : undefined;
470
+ return typeof reachablityClusterReqResp === 'number'
471
+ ? Math.floor(reachablityClusterReqResp)
472
+ : undefined;
458
473
  }
459
474
 
460
475
  /**
@@ -477,7 +492,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
477
492
  public getExchangeCITokenJMT() {
478
493
  const exchangeCITokenJMT = this.precomputedLatencies.get('internal.exchange.ci.token.time');
479
494
 
480
- return exchangeCITokenJMT ? Math.floor(exchangeCITokenJMT) : undefined;
495
+ return typeof exchangeCITokenJMT === 'number' ? Math.floor(exchangeCITokenJMT) : undefined;
481
496
  }
482
497
 
483
498
  /**
@@ -486,7 +501,9 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
486
501
  public getRefreshCaptchaReqResp() {
487
502
  const refreshCaptchaReqResp = this.precomputedLatencies.get('internal.refresh.captcha.time');
488
503
 
489
- return refreshCaptchaReqResp ? Math.floor(refreshCaptchaReqResp) : undefined;
504
+ return typeof refreshCaptchaReqResp === 'number'
505
+ ? Math.floor(refreshCaptchaReqResp)
506
+ : undefined;
490
507
  }
491
508
 
492
509
  /**
@@ -498,7 +515,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
498
515
  'internal.api.fetch.intelligence.models'
499
516
  );
500
517
 
501
- return downloadIntelligenceModelsReqResp
518
+ return typeof downloadIntelligenceModelsReqResp === 'number'
502
519
  ? Math.floor(downloadIntelligenceModelsReqResp)
503
520
  : undefined;
504
521
  }
@@ -40,6 +40,7 @@ import {
40
40
  ClientEventPayloadError,
41
41
  ClientSubServiceType,
42
42
  BrowserLaunchMethodType,
43
+ DelayedClientEvent,
43
44
  } from '../metrics.types';
44
45
  import CallDiagnosticEventsBatcher from './call-diagnostic-metrics-batcher';
45
46
  import PreLoginMetricsBatcher from '../prelogin-metrics-batcher';
@@ -95,6 +96,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
95
96
  private logger: any; // to avoid adding @ts-ignore everywhere
96
97
  private hasLoggedBrowserSerial: boolean;
97
98
  private device: any;
99
+ private delayedClientEvents: DelayedClientEvent[] = [];
98
100
 
99
101
  // the default validator before piping an event to the batcher
100
102
  // this function can be overridden by the user
@@ -165,9 +167,23 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
165
167
  return WEBEX_SUB_SERVICE_TYPES.SCHEDULED_MEETING;
166
168
  }
167
169
  // if Scheduled, Webinar, not pmr - then Webinar
168
- if (meetingInfo?.webexScheduled && meetingInfo?.enableEvent && !meetingInfo?.pmr) {
170
+ if (
171
+ meetingInfo?.webexScheduled &&
172
+ meetingInfo?.enableEvent &&
173
+ !meetingInfo?.pmr &&
174
+ meetingInfo?.isConvergedWebinar
175
+ ) {
169
176
  return WEBEX_SUB_SERVICE_TYPES.WEBINAR;
170
177
  }
178
+ // if Scheduled, Webinar enable webcast - then webcast
179
+ if (
180
+ meetingInfo?.webexScheduled &&
181
+ meetingInfo?.enableEvent &&
182
+ !meetingInfo?.pmr &&
183
+ meetingInfo?.isConvergedWebinarWebcast
184
+ ) {
185
+ return WEBEX_SUB_SERVICE_TYPES.WEBCAST;
186
+ }
171
187
  }
172
188
 
173
189
  return undefined;
@@ -716,6 +732,8 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
716
732
  meetingId,
717
733
  }),
718
734
  webexSubServiceType: this.getSubServiceType(meeting),
735
+ // @ts-ignore
736
+ webClientPreload: this.webex.meetings?.config?.metrics?.webClientPreload,
719
737
  };
720
738
 
721
739
  const joinFlowVersion = options.joinFlowVersion ?? meeting.callStateForMetrics?.joinFlowVersion;
@@ -768,6 +786,8 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
768
786
  webClientDomain: window.location.hostname,
769
787
  },
770
788
  loginType: this.getCurLoginType(),
789
+ // @ts-ignore
790
+ webClientPreload: this.webex.meetings?.config?.metrics?.webClientPreload,
771
791
  };
772
792
 
773
793
  if (options.joinFlowVersion) {
@@ -842,17 +862,36 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
842
862
  * @param arg.event - event key
843
863
  * @param arg.payload - additional payload to be merged with default payload
844
864
  * @param arg.options - payload
865
+ * @param arg.delaySubmitEvent - a boolean value indicating whether to delay the submission of client events.
845
866
  * @throws
846
867
  */
847
868
  public submitClientEvent({
848
869
  name,
849
870
  payload,
850
871
  options,
872
+ delaySubmitEvent,
851
873
  }: {
852
874
  name: ClientEvent['name'];
853
875
  payload?: ClientEventPayload;
854
876
  options?: SubmitClientEventOptions;
877
+ delaySubmitEvent?: boolean;
855
878
  }) {
879
+ if (delaySubmitEvent) {
880
+ // Preserve the time when the event was triggered if delaying the submission to Call Diagnostics
881
+ const delayedOptions = {
882
+ ...options,
883
+ triggeredTime: new Date().toISOString(),
884
+ };
885
+
886
+ this.delayedClientEvents.push({
887
+ name,
888
+ payload,
889
+ options: delayedOptions,
890
+ });
891
+
892
+ return Promise.resolve();
893
+ }
894
+
856
895
  this.logger.log(
857
896
  CALL_DIAGNOSTIC_LOG_IDENTIFIER,
858
897
  'CallDiagnosticMetrics: @submitClientEvent. Submit Client Event CA event.',
@@ -869,6 +908,28 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
869
908
  return this.submitToCallDiagnostics(diagnosticEvent);
870
909
  }
871
910
 
911
+ /**
912
+ * Submit Delayed Client Event CA events. Clears delayedClientEvents array after submission.
913
+ */
914
+ public submitDelayedClientEvents() {
915
+ this.logger.log(
916
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
917
+ 'CallDiagnosticMetrics: @submitDelayedClientEvents. Submitting delayed client events.'
918
+ );
919
+
920
+ if (this.delayedClientEvents.length === 0) {
921
+ return Promise.resolve();
922
+ }
923
+
924
+ const promises = this.delayedClientEvents.map((delayedSubmitClientEventParams) => {
925
+ return this.submitClientEvent(delayedSubmitClientEventParams);
926
+ });
927
+
928
+ this.delayedClientEvents = [];
929
+
930
+ return Promise.all(promises);
931
+ }
932
+
872
933
  /**
873
934
  * Prepare the event and send the request to metrics-a service.
874
935
  * @param event
@@ -17,12 +17,14 @@ export const DTLS_HANDSHAKE_FAILED_CLIENT_CODE = 2008;
17
17
  export const ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE = 2010;
18
18
  export const ICE_FAILED_WITHOUT_TURN_TLS_CLIENT_CODE = 2009;
19
19
  export const ICE_AND_REACHABILITY_FAILED_CLIENT_CODE = 2011;
20
+ export const MULTISTREAM_NOT_AVAILABLE_CLIENT_CODE = 2012;
20
21
  export const WBX_APP_API_URL = 'wbxappapi'; // MeetingInfo WebexAppApi response object normally contains a body.url that includes the string 'wbxappapi'
21
22
 
22
23
  export const WEBEX_SUB_SERVICE_TYPES: Record<string, ClientSubServiceType> = {
23
24
  PMR: 'PMR',
24
25
  SCHEDULED_MEETING: 'ScheduledMeeting',
25
26
  WEBINAR: 'Webinar',
27
+ WEBCAST: 'Webcast',
26
28
  };
27
29
 
28
30
  // Found in https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
@@ -127,6 +129,7 @@ export const ERROR_DESCRIPTIONS = {
127
129
  ICE_FAILED_WITHOUT_TURN_TLS: 'ICEFailedWithoutTURN_TLS',
128
130
  ICE_FAILED_WITH_TURN_TLS: 'ICEFailedWithTURN_TLS',
129
131
  ICE_AND_REACHABILITY_FAILED: 'ICEAndReachabilityFailed',
132
+ MULTISTREAM_NOT_AVAILABLE: 'MultistreamNotAvailable',
130
133
  SDP_OFFER_CREATION_ERROR: 'SdpOfferCreationError',
131
134
  SDP_OFFER_CREATION_ERROR_MISSING_CODEC: 'SdpOfferCreationErrorMissingCodec',
132
135
  WDM_RESTRICTED_REGION: 'WdmRestrictedRegion',
@@ -409,6 +412,11 @@ export const CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD: Record<number, Partial<ClientEv
409
412
  category: 'expected',
410
413
  fatal: true,
411
414
  },
415
+ [MULTISTREAM_NOT_AVAILABLE_CLIENT_CODE]: {
416
+ errorDescription: ERROR_DESCRIPTIONS.MULTISTREAM_NOT_AVAILABLE,
417
+ category: 'expected',
418
+ fatal: false,
419
+ },
412
420
  2050: {
413
421
  errorDescription: ERROR_DESCRIPTIONS.SDP_OFFER_CREATION_ERROR,
414
422
  category: 'media',
@@ -320,3 +320,9 @@ export interface IMetricsAttributes {
320
320
  meetingId?: string;
321
321
  callId?: string;
322
322
  }
323
+
324
+ export interface DelayedClientEvent {
325
+ name: ClientEvent['name'];
326
+ payload?: RecursivePartial<ClientEvent['payload']>;
327
+ options?: SubmitClientEventOptions;
328
+ }
@@ -45,6 +45,11 @@ class Metrics extends WebexPlugin {
45
45
  businessMetrics: BusinessMetrics;
46
46
  isReady = false;
47
47
 
48
+ /**
49
+ * Whether or not to delay the submission of client events.
50
+ */
51
+ delaySubmitClientEvents = false;
52
+
48
53
  /**
49
54
  * Constructor
50
55
  * @param args
@@ -290,7 +295,12 @@ class Metrics extends WebexPlugin {
290
295
  options: {meetingId: options?.meetingId},
291
296
  });
292
297
 
293
- return this.callDiagnosticMetrics.submitClientEvent({name, payload, options});
298
+ return this.callDiagnosticMetrics.submitClientEvent({
299
+ name,
300
+ payload,
301
+ options,
302
+ delaySubmitEvent: this.delaySubmitClientEvents,
303
+ });
294
304
  }
295
305
 
296
306
  /**
@@ -389,6 +399,22 @@ class Metrics extends WebexPlugin {
389
399
  public isServiceErrorExpected(serviceErrorCode: number): boolean {
390
400
  return this.callDiagnosticMetrics.isServiceErrorExpected(serviceErrorCode);
391
401
  }
402
+
403
+ /**
404
+ * Sets the value of delaySubmitClientEvents. If set to true, client events will be delayed until submitDelayedClientEvents is called. If
405
+ * set to false, delayed client events will be submitted.
406
+ *
407
+ * @param {boolean} shouldDelay - A boolean value indicating whether to delay the submission of client events.
408
+ */
409
+ public setDelaySubmitClientEvents(shouldDelay: boolean) {
410
+ this.delaySubmitClientEvents = shouldDelay;
411
+
412
+ if (!shouldDelay) {
413
+ return this.callDiagnosticMetrics.submitDelayedClientEvents();
414
+ }
415
+
416
+ return Promise.resolve();
417
+ }
392
418
  }
393
419
 
394
420
  export default Metrics;
@@ -437,6 +437,15 @@ describe('internal-plugin-metrics', () => {
437
437
  assert.deepEqual(cdl.getClickToInterstitial(), 5);
438
438
  });
439
439
 
440
+ it('calculates getClickToInterstitial without join button timestamp when it is 0', () => {
441
+ cdl.saveLatency('internal.click.to.interstitial', 0);
442
+ cdl.saveTimestamp({
443
+ key: 'internal.client.meeting.interstitial-window.showed',
444
+ value: 20,
445
+ });
446
+ assert.deepEqual(cdl.getClickToInterstitial(), 0);
447
+ });
448
+
440
449
  it('calculates getInterstitialToJoinOK correctly', () => {
441
450
  cdl.saveTimestamp({
442
451
  key: 'internal.client.interstitial-window.click.joinbutton',
@@ -449,6 +458,18 @@ describe('internal-plugin-metrics', () => {
449
458
  assert.deepEqual(cdl.getInterstitialToJoinOK(), 10);
450
459
  });
451
460
 
461
+ it('calculates getInterstitialToJoinOK correctly when one value is not a number', () => {
462
+ cdl.saveTimestamp({
463
+ key: 'internal.client.interstitial-window.click.joinbutton',
464
+ value: 'ten' as unknown as number,
465
+ });
466
+ cdl.saveTimestamp({
467
+ key: 'client.locus.join.response',
468
+ value: 20,
469
+ });
470
+ assert.deepEqual(cdl.getInterstitialToJoinOK(), undefined);
471
+ });
472
+
452
473
  it('calculates getCallInitMediaEngineReady correctly', () => {
453
474
  cdl.saveTimestamp({
454
475
  key: 'internal.client.interstitial-window.click.joinbutton',
@@ -481,6 +502,58 @@ describe('internal-plugin-metrics', () => {
481
502
  assert.deepEqual(cdl.getTotalJMT(), 45);
482
503
  });
483
504
 
505
+ it('calculates getTotalJMT correctly when clickToInterstitial is 0', () => {
506
+ cdl.saveLatency('internal.click.to.interstitial', 0);
507
+ cdl.saveTimestamp({
508
+ key: 'internal.client.interstitial-window.click.joinbutton',
509
+ value: 20,
510
+ });
511
+ cdl.saveTimestamp({
512
+ key: 'client.locus.join.response',
513
+ value: 40,
514
+ });
515
+ assert.deepEqual(cdl.getTotalJMT(), 20);
516
+ });
517
+
518
+ it('calculates getTotalJMT correctly when interstitialToJoinOk is 0', () => {
519
+ cdl.saveTimestamp({
520
+ key: 'internal.client.interstitial-window.click.joinbutton',
521
+ value: 40,
522
+ });
523
+ cdl.saveLatency('internal.click.to.interstitial', 12);
524
+ cdl.saveTimestamp({
525
+ key: 'client.locus.join.response',
526
+ value: 40,
527
+ });
528
+ assert.deepEqual(cdl.getTotalJMT(), 12);
529
+ });
530
+
531
+ it('calculates getTotalJMT correctly when both clickToInterstitial and interstitialToJoinOk are 0', () => {
532
+ cdl.saveTimestamp({
533
+ key: 'internal.client.interstitial-window.click.joinbutton',
534
+ value: 40,
535
+ });
536
+ cdl.saveLatency('internal.click.to.interstitial', 0);
537
+ cdl.saveTimestamp({
538
+ key: 'client.locus.join.response',
539
+ value: 40,
540
+ });
541
+ assert.deepEqual(cdl.getTotalJMT(), 0);
542
+ });
543
+
544
+ it('calculates getTotalJMT correctly when both clickToInterstitial is not a number', () => {
545
+ cdl.saveTimestamp({
546
+ key: 'internal.client.interstitial-window.click.joinbutton',
547
+ value: 40,
548
+ });
549
+ cdl.saveLatency('internal.click.to.interstitial', 'eleven' as unknown as number);
550
+ cdl.saveTimestamp({
551
+ key: 'client.locus.join.response',
552
+ value: 40,
553
+ });
554
+ assert.deepEqual(cdl.getTotalJMT(), undefined);
555
+ });
556
+
484
557
  it('calculates getTotalMediaJMT correctly', () => {
485
558
  cdl.saveTimestamp({
486
559
  key: 'internal.client.meeting.click.joinbutton',