@webex/internal-plugin-metrics 3.12.0-task-refactor.1 → 3.12.0-webex-services-ready.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.
Files changed (56) hide show
  1. package/dist/batcher.js +3 -0
  2. package/dist/batcher.js.map +1 -1
  3. package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js +23 -0
  4. package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js.map +1 -1
  5. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +68 -50
  6. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
  7. package/dist/call-diagnostic/call-diagnostic-metrics.js +60 -8
  8. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
  9. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +44 -4
  10. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
  11. package/dist/call-diagnostic/config.js +17 -3
  12. package/dist/call-diagnostic/config.js.map +1 -1
  13. package/dist/config.js +1 -0
  14. package/dist/config.js.map +1 -1
  15. package/dist/generic-metrics.js +8 -6
  16. package/dist/generic-metrics.js.map +1 -1
  17. package/dist/index.js +7 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/metrics.js +1 -1
  20. package/dist/metrics.types.js.map +1 -1
  21. package/dist/new-metrics.js +56 -20
  22. package/dist/new-metrics.js.map +1 -1
  23. package/dist/prelogin-metrics-batcher.js +23 -0
  24. package/dist/prelogin-metrics-batcher.js.map +1 -1
  25. package/dist/prelogin-metrics.js +106 -0
  26. package/dist/prelogin-metrics.js.map +1 -0
  27. package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +9 -0
  28. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +57 -20
  29. package/dist/types/call-diagnostic/call-diagnostic-metrics.util.d.ts +23 -2
  30. package/dist/types/call-diagnostic/config.d.ts +6 -19
  31. package/dist/types/config.d.ts +16 -15
  32. package/dist/types/index.d.ts +2 -1
  33. package/dist/types/metrics.types.d.ts +4 -4
  34. package/dist/types/new-metrics.d.ts +12 -0
  35. package/dist/types/prelogin-metrics.d.ts +47 -0
  36. package/package.json +11 -11
  37. package/src/batcher.js +4 -0
  38. package/src/call-diagnostic/call-diagnostic-metrics-batcher.ts +26 -0
  39. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +139 -70
  40. package/src/call-diagnostic/call-diagnostic-metrics.ts +52 -1
  41. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +49 -2
  42. package/src/call-diagnostic/config.ts +15 -0
  43. package/src/config.js +1 -0
  44. package/src/generic-metrics.ts +6 -6
  45. package/src/index.ts +2 -0
  46. package/src/metrics.types.ts +3 -3
  47. package/src/new-metrics.ts +42 -0
  48. package/src/prelogin-metrics-batcher.ts +26 -0
  49. package/src/prelogin-metrics.ts +94 -0
  50. package/test/unit/spec/batcher.js +43 -0
  51. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +183 -11
  52. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +324 -366
  53. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +724 -159
  54. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +92 -3
  55. package/test/unit/spec/prelogin-metrics-batcher.ts +190 -36
  56. package/test/unit/spec/prelogin-metrics.ts +132 -0
@@ -0,0 +1,47 @@
1
+ import GenericMetrics from './generic-metrics';
2
+ import { EventPayload } from './metrics.types';
3
+ import PreLoginMetricsBatcher from './prelogin-metrics-batcher';
4
+ /**
5
+ * @description Util class to handle PreLogin Metrics
6
+ * @export
7
+ * @class PreLoginMetrics
8
+ */
9
+ export default class PreLoginMetrics extends GenericMetrics {
10
+ private preLoginMetricsBatcher;
11
+ /**
12
+ * Constructor
13
+ * @param {PreLoginMetricsBatcher} preLoginMetricsBatcher - Pre-login metrics batcher
14
+ * @param {any} attrs - Attributes
15
+ * @param {any} options - Options
16
+ * @constructor
17
+ */
18
+ constructor(preLoginMetricsBatcher: typeof PreLoginMetricsBatcher, attrs?: any, options?: {
19
+ parent?: any;
20
+ });
21
+ /**
22
+ * Submit a business metric to our metrics endpoint.
23
+ * Routes to the correct table with the correct schema payload by table.
24
+ * @see https://confluence-eng-gpk2.cisco.com/conf/display/WAP/Business+metrics++-%3E+ROMA
25
+ * @param {Object} options - The options object
26
+ * @param {string} options.name - Name of the metric
27
+ * @param {string} options.preLoginId - ID to identify pre-login user
28
+ * @param {EventPayload} options.payload - User payload of the metric
29
+ * @param {EventPayload} [options.metadata] - Optional metadata to include outside of eventPayload.value
30
+ * @returns {Promise<void>} Promise that resolves when the metric is submitted
31
+ */
32
+ submitPreLoginEvent({ name, preLoginId, payload, metadata, }: {
33
+ name: string;
34
+ preLoginId: string;
35
+ payload: EventPayload;
36
+ metadata?: EventPayload;
37
+ }): Promise<void>;
38
+ /**
39
+ * Builds a formatted event object for metrics submission.
40
+ * @param {string} metricName - Metric name
41
+ * @param {string} preLoginId - Pre-login user identifier
42
+ * @param {EventPayload} payload - Metric payload data
43
+ * @param {EventPayload} metadata - Additional metadata to include in the event
44
+ * @returns {object} Formatted metrics event object with type, eventPayload, and timestamp
45
+ */
46
+ private buildEvent;
47
+ }
package/package.json CHANGED
@@ -24,23 +24,23 @@
24
24
  "@sinonjs/fake-timers": "^6.0.1",
25
25
  "@webex/babel-config-legacy": "0.0.0",
26
26
  "@webex/eslint-config-legacy": "0.0.0",
27
+ "@webex/event-dictionary-ts": "^1.0.2191",
27
28
  "@webex/jest-config-legacy": "0.0.0",
28
29
  "@webex/legacy-tools": "0.0.0",
29
- "@webex/test-helper-chai": "3.12.0-task-refactor.1",
30
- "@webex/test-helper-mocha": "3.12.0-task-refactor.1",
31
- "@webex/test-helper-mock-webex": "3.12.0-task-refactor.1",
32
- "@webex/test-helper-test-users": "3.12.0-task-refactor.1",
30
+ "@webex/test-helper-chai": "3.12.0-webex-services-ready.1",
31
+ "@webex/test-helper-mocha": "3.12.0-webex-services-ready.1",
32
+ "@webex/test-helper-mock-webex": "3.12.0-webex-services-ready.1",
33
+ "@webex/test-helper-test-users": "3.12.0-webex-services-ready.1",
33
34
  "eslint": "^8.24.0",
34
35
  "prettier": "^2.7.1",
35
36
  "sinon": "^9.2.4"
36
37
  },
37
38
  "dependencies": {
38
- "@webex/common": "3.12.0-task-refactor.1",
39
- "@webex/common-timers": "3.12.0-task-refactor.1",
40
- "@webex/event-dictionary-ts": "^1.0.1930",
41
- "@webex/test-helper-chai": "3.12.0-task-refactor.1",
42
- "@webex/test-helper-mock-webex": "3.12.0-task-refactor.1",
43
- "@webex/webex-core": "3.12.0-task-refactor.1",
39
+ "@webex/common": "3.12.0-webex-services-ready.1",
40
+ "@webex/common-timers": "3.12.0-webex-services-ready.1",
41
+ "@webex/test-helper-chai": "3.12.0-webex-services-ready.1",
42
+ "@webex/test-helper-mock-webex": "3.12.0-webex-services-ready.1",
43
+ "@webex/webex-core": "3.12.0-webex-services-ready.1",
44
44
  "ip-anonymize": "^0.1.0",
45
45
  "lodash": "^4.17.21",
46
46
  "uuid": "^3.3.2"
@@ -53,5 +53,5 @@
53
53
  "test:style": "eslint ./src/**/*.*",
54
54
  "test:unit": "webex-legacy-tools test --unit --runner mocha"
55
55
  },
56
- "version": "3.12.0-task-refactor.1"
56
+ "version": "3.12.0-webex-services-ready.1"
57
57
  }
package/src/batcher.js CHANGED
@@ -75,6 +75,10 @@ const MetricsBatcher = Batcher.extend({
75
75
  */
76
76
  handleHttpError(reason) {
77
77
  if (reason instanceof WebexHttpError.NetworkOrCORSError) {
78
+ if (!this.config.batcherRetryOnNetworkError) {
79
+ return Reflect.apply(Batcher.prototype.handleHttpError, this, [reason]);
80
+ }
81
+
78
82
  this.logger.warn(
79
83
  'metrics-batcher: received network error submitting metrics, reenqueuing payload'
80
84
  );
@@ -55,6 +55,8 @@ const CallDiagnosticEventsBatcher = Batcher.extend({
55
55
  `CallDiagnosticEventsBatcher: @submitHttpRequest#${batchId}. Request successful.`
56
56
  );
57
57
 
58
+ this.handleHttpResponseStatus(res?.statusCode, payload);
59
+
58
60
  return res;
59
61
  })
60
62
  .catch((err) => {
@@ -64,9 +66,33 @@ const CallDiagnosticEventsBatcher = Batcher.extend({
64
66
  `error: ${generateCommonErrorMetadata(err)}`
65
67
  );
66
68
 
69
+ this.handleHttpResponseStatus(err?.statusCode, payload);
70
+
67
71
  return Promise.reject(err);
68
72
  });
69
73
  },
74
+
75
+ /**
76
+ * React to the HTTP status code returned by the metrics endpoint.
77
+ * Only items submitted with `markTelemetryOptOutOnResponse: true` opt into
78
+ * this behavior.
79
+ * @param {number | undefined} statusCode
80
+ * @param {any[]} payload Items flushed in this HTTP batch.
81
+ * @returns {void}
82
+ */
83
+ handleHttpResponseStatus(statusCode: number | undefined, payload: any[]) {
84
+ const shouldMark =
85
+ Array.isArray(payload) &&
86
+ payload.some((item) => item?.markTelemetryOptOutOnResponse === true);
87
+
88
+ if (!shouldMark) {
89
+ return;
90
+ }
91
+
92
+ if (statusCode === 200) {
93
+ this.webex.internal.newMetrics?.callDiagnosticMetrics?.setIsTelemetryOptOutAutomatic(true);
94
+ }
95
+ },
70
96
  });
71
97
 
72
98
  export default CallDiagnosticEventsBatcher;
@@ -193,7 +193,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
193
193
  */
194
194
  public getShowInterstitialTime() {
195
195
  return this.getDiffBetweenTimestamps(
196
- 'client.interstitial-window.start-launch',
196
+ 'internal.client.meeting.interstitial-window.showed',
197
197
  'internal.client.interstitial-window.click.joinbutton'
198
198
  );
199
199
  }
@@ -224,11 +224,17 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
224
224
  * @returns - latency
225
225
  */
226
226
  public getCallInitJoinReq() {
227
- return this.getDiffBetweenTimestamps(
228
- 'internal.client.interstitial-window.click.joinbutton',
229
- 'client.locus.join.request',
230
- {maximum: 1200000}
227
+ const interstitialShowedToJoinReq = this.getDiffBetweenTimestamps(
228
+ 'internal.client.meeting.interstitial-window.showed',
229
+ 'client.locus.join.request'
231
230
  );
231
+ const showInterstitialTime = this.getShowInterstitialTime() || 0;
232
+
233
+ if (typeof interstitialShowedToJoinReq !== 'number') {
234
+ return undefined;
235
+ }
236
+
237
+ return clamp(interstitialShowedToJoinReq - showInterstitialTime, 0, 1200000);
232
238
  }
233
239
 
234
240
  /**
@@ -303,10 +309,39 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
303
309
  * @returns - latency
304
310
  */
305
311
  public getStayLobbyTime() {
306
- return this.getDiffBetweenTimestamps(
307
- 'client.locus.join.response',
308
- 'internal.host.meeting.participant.admitted'
309
- );
312
+ return this.getDiffBetweenTimestamps('client.lobby.entered', 'client.lobby.exited');
313
+ }
314
+
315
+ /**
316
+ * Stay lobby time capped by a certain timestamp.
317
+ * This is to handle the case where the target end timestamp could happen before the lobby is exited,
318
+ * for example media-engine.ready or client.ice.end
319
+ * This is supposed to be called AFTER the end timestamp happens
320
+ * @param endTimestampKey name of the target end event
321
+ * @returns - latency
322
+ */
323
+ public getStayLobbyTimeCappedBy(endTimestampKey: MetricEventNames) {
324
+ const lobbyStartTimestamp = this.latencyTimestamps.get('client.lobby.entered'); // might not exist (some meetings don't have lobby)
325
+
326
+ if (typeof lobbyStartTimestamp !== 'number') {
327
+ // no lobby in the meeting, stayLobbyTime is 0
328
+ return 0;
329
+ }
330
+
331
+ const lobbyEndTimestamp = this.latencyTimestamps.get('client.lobby.exited'); // might not exist (if user still in lobby at the time of measurement)
332
+ const maximumEndTimestamp = this.latencyTimestamps.get(endTimestampKey); // must exist
333
+
334
+ if (typeof maximumEndTimestamp !== 'number') {
335
+ // the provided timestamp to be used as a cap should exist, return undefined if it doesn't
336
+ return undefined;
337
+ }
338
+
339
+ const endTimestamp =
340
+ typeof lobbyEndTimestamp === 'number'
341
+ ? Math.min(lobbyEndTimestamp, maximumEndTimestamp)
342
+ : maximumEndTimestamp;
343
+
344
+ return clamp(endTimestamp - lobbyStartTimestamp, 0, this.MAX_INTEGER);
310
345
  }
311
346
 
312
347
  /**
@@ -334,14 +369,6 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
334
369
  * @returns - latency
335
370
  */
336
371
  public getClickToInterstitial() {
337
- // for normal join (where green join button exists before interstitial, i.e reminder, space list etc)
338
- if (this.latencyTimestamps.get('internal.client.meeting.click.joinbutton')) {
339
- return this.getDiffBetweenTimestamps(
340
- 'internal.client.meeting.click.joinbutton',
341
- 'internal.client.meeting.interstitial-window.showed'
342
- );
343
- }
344
-
345
372
  const clickToInterstitialLatency = this.precomputedLatencies.get(
346
373
  'internal.click.to.interstitial'
347
374
  );
@@ -358,14 +385,6 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
358
385
  * @returns - latency
359
386
  */
360
387
  public getClickToInterstitialWithUserDelay() {
361
- // for normal join (where green join button exists before interstitial, i.e reminder, space list etc)
362
- if (this.latencyTimestamps.get('internal.client.meeting.click.joinbutton')) {
363
- return this.getDiffBetweenTimestamps(
364
- 'internal.client.meeting.click.joinbutton',
365
- 'internal.client.meeting.interstitial-window.showed'
366
- );
367
- }
368
-
369
388
  const clickToInterstitialWithUserDelayLatency = this.precomputedLatencies.get(
370
389
  'internal.click.to.interstitial.with.user.delay'
371
390
  );
@@ -382,10 +401,17 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
382
401
  * @returns - latency
383
402
  */
384
403
  public getInterstitialToJoinOK() {
385
- return this.getDiffBetweenTimestamps(
386
- 'internal.client.interstitial-window.click.joinbutton',
404
+ const interstitialShowedToJoinResp = this.getDiffBetweenTimestamps(
405
+ 'internal.client.meeting.interstitial-window.showed',
387
406
  'client.locus.join.response'
388
407
  );
408
+ const showInterstitialTime = this.getShowInterstitialTime() || 0;
409
+
410
+ if (typeof interstitialShowedToJoinResp !== 'number') {
411
+ return undefined;
412
+ }
413
+
414
+ return clamp(interstitialShowedToJoinResp - showInterstitialTime, 0, this.MAX_INTEGER);
389
415
  }
390
416
 
391
417
  /**
@@ -393,11 +419,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
393
419
  * @returns - latency
394
420
  */
395
421
  public getCallInitMediaEngineReady() {
396
- return this.getDiffBetweenTimestamps(
397
- 'internal.client.interstitial-window.click.joinbutton',
398
- 'client.media-engine.ready',
399
- {maximum: 1200000}
400
- );
422
+ return this.getInterstitialToMediaOKJMT();
401
423
  }
402
424
 
403
425
  /**
@@ -405,20 +427,22 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
405
427
  * @returns - latency
406
428
  */
407
429
  public getInterstitialToMediaOKJMT() {
408
- const interstitialJoinClickTimestamp = this.latencyTimestamps.get(
409
- 'internal.client.interstitial-window.click.joinbutton'
430
+ const interstitialShowedToIceEnd = this.getDiffBetweenTimestamps(
431
+ 'internal.client.meeting.interstitial-window.showed',
432
+ 'client.ice.end'
410
433
  );
434
+ const showInterstitialTime = this.getShowInterstitialTime() || 0;
435
+ const stayLobbyTimeCappedByIceEnd = this.getStayLobbyTimeCappedBy('client.ice.end');
411
436
 
412
- // get the first timestamp
413
- const connectedMedia = this.latencyTimestamps.get('client.ice.end');
414
-
415
- const lobbyTimeLatency = this.getStayLobbyTime();
416
- const lobbyTime = typeof lobbyTimeLatency === 'number' ? lobbyTimeLatency : 0;
417
-
418
- if (interstitialJoinClickTimestamp && connectedMedia) {
419
- const interstitialToMediaOKJmt = connectedMedia - interstitialJoinClickTimestamp - lobbyTime;
420
-
421
- return clamp(interstitialToMediaOKJmt, 0, this.MAX_INTEGER);
437
+ if (
438
+ typeof interstitialShowedToIceEnd === 'number' &&
439
+ typeof stayLobbyTimeCappedByIceEnd === 'number'
440
+ ) {
441
+ return clamp(
442
+ interstitialShowedToIceEnd - showInterstitialTime - stayLobbyTimeCappedByIceEnd,
443
+ 0,
444
+ this.MAX_INTEGER
445
+ );
422
446
  }
423
447
 
424
448
  return undefined;
@@ -430,10 +454,21 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
430
454
  */
431
455
  public getTotalJMT() {
432
456
  const clickToInterstitial = this.getClickToInterstitial();
433
- const interstitialToJoinOk = this.getInterstitialToJoinOK();
457
+ const interstitialShowedToJoinLocusResponse = this.getDiffBetweenTimestamps(
458
+ 'internal.client.meeting.interstitial-window.showed',
459
+ 'client.locus.join.response'
460
+ );
461
+ const showInterstitialTime = this.getShowInterstitialTime() || 0;
434
462
 
435
- if (typeof clickToInterstitial === 'number' && typeof interstitialToJoinOk === 'number') {
436
- return clamp(clickToInterstitial + interstitialToJoinOk, 0, this.MAX_INTEGER);
463
+ if (
464
+ typeof clickToInterstitial === 'number' &&
465
+ typeof interstitialShowedToJoinLocusResponse === 'number'
466
+ ) {
467
+ return clamp(
468
+ clickToInterstitial + interstitialShowedToJoinLocusResponse - showInterstitialTime,
469
+ 0,
470
+ this.MAX_INTEGER
471
+ );
437
472
  }
438
473
 
439
474
  return undefined;
@@ -445,13 +480,20 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
445
480
  */
446
481
  public getTotalJMTWithUserDelay() {
447
482
  const clickToInterstitialWithUserDelay = this.getClickToInterstitialWithUserDelay();
448
- const interstitialToJoinOk = this.getInterstitialToJoinOK();
483
+ const interstitialShowedToJoinLocusResponse = this.getDiffBetweenTimestamps(
484
+ 'internal.client.meeting.interstitial-window.showed',
485
+ 'client.locus.join.response'
486
+ );
449
487
 
450
488
  if (
451
489
  typeof clickToInterstitialWithUserDelay === 'number' &&
452
- typeof interstitialToJoinOk === 'number'
490
+ typeof interstitialShowedToJoinLocusResponse === 'number'
453
491
  ) {
454
- return clamp(clickToInterstitialWithUserDelay + interstitialToJoinOk, 0, this.MAX_INTEGER);
492
+ return clamp(
493
+ clickToInterstitialWithUserDelay + interstitialShowedToJoinLocusResponse,
494
+ 0,
495
+ this.MAX_INTEGER
496
+ );
455
497
  }
456
498
 
457
499
  return undefined;
@@ -478,21 +520,28 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
478
520
  */
479
521
  public getTotalMediaJMT() {
480
522
  const clickToInterstitial = this.getClickToInterstitial();
481
- const interstitialToJoinOk = this.getInterstitialToJoinOK();
482
- const joinConfJMT = this.getJoinConfJMT();
483
- const lobbyTime = this.getStayLobbyTime();
523
+ const interstitialShowedToMediaEngineReady = this.getDiffBetweenTimestamps(
524
+ 'internal.client.meeting.interstitial-window.showed',
525
+ 'client.media-engine.ready'
526
+ );
527
+ const showInterstitialTime = this.getShowInterstitialTime() || 0;
528
+ const stayLobbyTimeCappedByMediaEngineReady = this.getStayLobbyTimeCappedBy(
529
+ 'client.media-engine.ready'
530
+ );
484
531
 
485
- if (clickToInterstitial && interstitialToJoinOk && joinConfJMT) {
486
- const totalMediaJMT = clamp(
487
- clickToInterstitial + interstitialToJoinOk + joinConfJMT,
532
+ if (
533
+ typeof clickToInterstitial === 'number' &&
534
+ typeof interstitialShowedToMediaEngineReady === 'number' &&
535
+ typeof stayLobbyTimeCappedByMediaEngineReady === 'number'
536
+ ) {
537
+ return clamp(
538
+ clickToInterstitial +
539
+ interstitialShowedToMediaEngineReady -
540
+ showInterstitialTime -
541
+ stayLobbyTimeCappedByMediaEngineReady,
488
542
  0,
489
- Infinity
543
+ this.MAX_INTEGER
490
544
  );
491
- if (this.getMeeting()?.allowMediaInLobby) {
492
- return clamp(totalMediaJMT, 0, this.MAX_INTEGER);
493
- }
494
-
495
- return clamp(totalMediaJMT - lobbyTime, 0, this.MAX_INTEGER);
496
545
  }
497
546
 
498
547
  return undefined;
@@ -504,12 +553,17 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
504
553
  */
505
554
  public getTotalMediaJMTWithUserDelay() {
506
555
  const clickToInterstitialWithUserDelay = this.getClickToInterstitialWithUserDelay();
507
- const interstitialToJoinOk = this.getInterstitialToJoinOK();
508
- const joinConfJMT = this.getJoinConfJMT();
556
+ const interstitialShowedToMediaEngineReady = this.getDiffBetweenTimestamps(
557
+ 'internal.client.meeting.interstitial-window.showed',
558
+ 'client.media-engine.ready'
559
+ );
509
560
 
510
- if (clickToInterstitialWithUserDelay && interstitialToJoinOk && joinConfJMT) {
561
+ if (
562
+ typeof clickToInterstitialWithUserDelay === 'number' &&
563
+ typeof interstitialShowedToMediaEngineReady === 'number'
564
+ ) {
511
565
  return clamp(
512
- clickToInterstitialWithUserDelay + interstitialToJoinOk + joinConfJMT,
566
+ clickToInterstitialWithUserDelay + interstitialShowedToMediaEngineReady,
513
567
  0,
514
568
  this.MAX_INTEGER
515
569
  );
@@ -523,11 +577,26 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
523
577
  * @returns - latency
524
578
  */
525
579
  public getClientJMT() {
526
- const interstitialToJoinOk = this.getInterstitialToJoinOK();
527
- const joinConfJMT = this.getJoinConfJMT();
580
+ const clickToInterstitialForClientJmt = this.precomputedLatencies.get(
581
+ 'internal.click.to.interstitial.for.client.jmt'
582
+ );
583
+ const interstitialShowedToLocusJoinRequest = this.getDiffBetweenTimestamps(
584
+ 'internal.client.meeting.interstitial-window.showed',
585
+ 'client.locus.join.request'
586
+ );
587
+ const showInterstitialTime = this.getShowInterstitialTime() || 0;
528
588
 
529
- if (typeof interstitialToJoinOk === 'number' && typeof joinConfJMT === 'number') {
530
- return clamp(interstitialToJoinOk - joinConfJMT, 0, this.MAX_INTEGER);
589
+ if (
590
+ typeof clickToInterstitialForClientJmt === 'number' &&
591
+ typeof interstitialShowedToLocusJoinRequest === 'number'
592
+ ) {
593
+ return clamp(
594
+ clickToInterstitialForClientJmt +
595
+ interstitialShowedToLocusJoinRequest -
596
+ showInterstitialTime,
597
+ 0,
598
+ this.MAX_INTEGER
599
+ );
531
600
  }
532
601
 
533
602
  return undefined;
@@ -108,6 +108,8 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
108
108
  private isMercuryConnected = false;
109
109
  private eventLimitTracker: Map<string, number> = new Map();
110
110
  private eventLimitWarningsLogged: Set<string> = new Set();
111
+ private isTelemetryOptOutManual = false;
112
+ private isTelemetryOptOutAutomatic = false;
111
113
 
112
114
  // the default validator before piping an event to the batcher
113
115
  // this function can be overridden by the user
@@ -145,6 +147,37 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
145
147
  return null;
146
148
  }
147
149
 
150
+ /**
151
+ * Returns the telemetryOptOut value of the current user
152
+ * @returns one of 'manual', 'automatic', undefined
153
+ */
154
+ public getTelemetryOptOut() {
155
+ if (this.isTelemetryOptOutManual) {
156
+ return 'manual';
157
+ }
158
+ if (this.isTelemetryOptOutAutomatic) {
159
+ return 'automatic';
160
+ }
161
+
162
+ return undefined;
163
+ }
164
+
165
+ /**
166
+ * Sets the manual telemetry opt-out status for the current user
167
+ * @param value - boolean value indicating manual telemetry opt-out status
168
+ */
169
+ public setIsTelemetryOptOutManual(value: boolean) {
170
+ this.isTelemetryOptOutManual = value;
171
+ }
172
+
173
+ /**
174
+ * Sets the automatic telemetry opt-out status for the current user
175
+ * @param value - boolean value indicating automatic telemetry opt-out status
176
+ */
177
+ public setIsTelemetryOptOutAutomatic(value: boolean) {
178
+ this.isTelemetryOptOutAutomatic = value;
179
+ }
180
+
148
181
  /**
149
182
  * Returns if the meeting has converged architecture enabled
150
183
  * @param options.meetingId
@@ -190,6 +223,11 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
190
223
 
191
224
  // if ConvergedArchitecture enable and isConvergedWebinarWebcast -- then webcast
192
225
  if (meetingInfo?.enableConvergedArchitecture && meetingInfo?.enableEvent) {
226
+ // if enableConvergedWebinarLargeScale - then large scale webinar
227
+ if (meetingInfo?.enableConvergedWebinarLargeScale) {
228
+ return WEBEX_SUB_SERVICE_TYPES.LARGE_SCALE_WEBINAR;
229
+ }
230
+
193
231
  return meetingInfo?.isConvergedWebinarWebcast
194
232
  ? WEBEX_SUB_SERVICE_TYPES.WEBCAST
195
233
  : WEBEX_SUB_SERVICE_TYPES.WEBINAR;
@@ -366,6 +404,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
366
404
  if (meeting?.locusInfo?.fullState) {
367
405
  identifiers.locusUrl = meeting.locusUrl;
368
406
  identifiers.locusId = meeting.locusUrl && meeting.locusUrl.split('/').pop();
407
+ identifiers.locusSessionId = meeting.locusInfo.fullState.sessionId;
369
408
  identifiers.locusStartTime =
370
409
  meeting.locusInfo.fullState && meeting.locusInfo.fullState.lastActive;
371
410
  }
@@ -601,6 +640,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
601
640
  mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
602
641
  startTime: new Date().toISOString(),
603
642
  },
643
+ webexSubServiceType: this.getSubServiceType(meeting),
604
644
  };
605
645
 
606
646
  // merge any new properties, or override existing ones
@@ -983,7 +1023,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
983
1023
  sessionCorrelationId,
984
1024
  });
985
1025
 
986
- // create common event object structur
1026
+ // create common event object structure
987
1027
  const commonEventObject = {
988
1028
  name,
989
1029
  canProceed: true,
@@ -996,6 +1036,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
996
1036
  'loginType' in meeting.callStateForMetrics
997
1037
  ? meeting.callStateForMetrics.loginType
998
1038
  : this.getCurLoginType(),
1039
+ telemetryOptOut: this.getTelemetryOptOut(),
999
1040
  isConvergedArchitectureEnabled: this.getIsConvergedArchitectureEnabled({
1000
1041
  meetingId,
1001
1042
  }),
@@ -1004,6 +1045,9 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1004
1045
  webexSubServiceType: this.getSubServiceType(meeting),
1005
1046
  // @ts-ignore
1006
1047
  webClientPreload: this.webex.meetings?.config?.metrics?.webClientPreload,
1048
+ isVipMeeting: meeting?.meetingInfo?.vipmeeting || false,
1049
+ isAutomatedUser:
1050
+ typeof window !== 'undefined' && typeof navigator !== 'undefined' && !!navigator?.webdriver, // if webdriver is true, it's most likely in a test environment
1007
1051
  };
1008
1052
 
1009
1053
  const joinFlowVersion = options.joinFlowVersion ?? meeting.callStateForMetrics?.joinFlowVersion;
@@ -1123,8 +1167,11 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1123
1167
  isMercuryConnected: this.isMercuryConnected,
1124
1168
  },
1125
1169
  loginType: this.getCurLoginType(),
1170
+ telemetryOptOut: this.getTelemetryOptOut(),
1126
1171
  // @ts-ignore
1127
1172
  webClientPreload: this.webex.meetings?.config?.metrics?.webClientPreload,
1173
+ isAutomatedUser:
1174
+ typeof window !== 'undefined' && typeof navigator !== 'undefined' && !!navigator?.webdriver, // if webdriver is true, it's most likely in a test environment
1128
1175
  };
1129
1176
 
1130
1177
  if (options.joinFlowVersion) {
@@ -1317,6 +1364,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1317
1364
  const finalEvent = {
1318
1365
  eventPayload: event,
1319
1366
  type: ['diagnostic-event'],
1367
+ markTelemetryOptOutOnResponse: true,
1320
1368
  };
1321
1369
 
1322
1370
  return this.callDiagnosticEventsBatcher.request(finalEvent);
@@ -1326,6 +1374,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1326
1374
  * Prepare the event and send the request to metrics-a service, pre login.
1327
1375
  * @param event
1328
1376
  * @param preLoginId
1377
+ * @param markTelemetryOptOutOnResponse
1329
1378
  * @returns
1330
1379
  */
1331
1380
  submitToCallDiagnosticsPreLogin = (event: Event, preLoginId?: string): Promise<any> => {
@@ -1333,7 +1382,9 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1333
1382
  const finalEvent = {
1334
1383
  eventPayload: event,
1335
1384
  type: ['diagnostic-event'],
1385
+ markTelemetryOptOutOnResponse: true,
1336
1386
  };
1387
+
1337
1388
  this.preLoginMetricsBatcher.savePreLoginId(preLoginId);
1338
1389
 
1339
1390
  return this.preLoginMetricsBatcher.request(finalEvent);
@@ -179,6 +179,43 @@ export const isSdpOfferCreationError = (rawError: any) => {
179
179
  return false;
180
180
  };
181
181
 
182
+ export const isWebrtcApiNotAvailableError = (
183
+ rawError: {code: number; message: string; name: string} | unknown
184
+ ) => {
185
+ if ((rawError as {name: string}).name === ERROR_DESCRIPTIONS.WEBRTC_API_NOT_AVAILABLE) {
186
+ return true;
187
+ }
188
+
189
+ return false;
190
+ };
191
+
192
+ /**
193
+ * Checks if the given error is a browser media error by its name.
194
+ * Returns true if the error name matches any known browser media error name in the mapping.
195
+ *
196
+ * @param {Object} rawError - The error object to check.
197
+ * @returns {boolean} True if the error is a browser media error, false otherwise.
198
+ */
199
+ export const isBrowserMediaError = (rawError) => {
200
+ // eslint-disable-next-line no-use-before-define
201
+ if (isBrowserMediaErrorName(rawError.name)) {
202
+ return true;
203
+ }
204
+
205
+ return false;
206
+ };
207
+
208
+ /**
209
+ * Returns the client error code mapped to the given browser media error name.
210
+ * If the error name is not found in the mapping, returns undefined.
211
+ *
212
+ * @param {Object} rawError - The error object containing the error name.
213
+ * @returns {string|undefined} The mapped client error code, or undefined if not found.
214
+ */
215
+ export const getBrowserMediaErrorCode = (rawError) => {
216
+ return BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP[rawError.name];
217
+ };
218
+
182
219
  /**
183
220
  * MDN Media Devices getUserMedia() method returns a name if it errs
184
221
  * Documentation can be found here: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
@@ -247,7 +284,8 @@ export const prepareDiagnosticMetricItem = (webex: any, item: any) => {
247
284
  devicePairingType: webex.devicemanager.getPairedMethod(),
248
285
  deviceURL: pairedDevice.url,
249
286
  isPersonalDevice: pairedDevice.mode === 'personal',
250
- productName: pairedDevice.devices[0]?.productName,
287
+ productName:
288
+ pairedDevice.devices?.length > 0 ? pairedDevice.devices[0]?.productName : undefined,
251
289
  };
252
290
  item.eventPayload.event.pairingState = 'paired';
253
291
  item.eventPayload.event.pairedDevice = devicePayload;
@@ -324,7 +362,6 @@ export const prepareDiagnosticMetricItem = (webex: any, item: any) => {
324
362
  joinTimes.totalMediaJMT = cdl.getTotalMediaJMT();
325
363
  joinTimes.interstitialToMediaOKJMT = cdl.getInterstitialToMediaOKJMT();
326
364
  joinTimes.callInitMediaEngineReady = cdl.getCallInitMediaEngineReady();
327
- joinTimes.stayLobbyTime = cdl.getStayLobbyTime();
328
365
  joinTimes.totalMediaJMTWithUserDelay = cdl.getTotalMediaJMTWithUserDelay();
329
366
  joinTimes.totalJMTWithUserDelay = cdl.getTotalJMTWithUserDelay();
330
367
  break;
@@ -332,6 +369,11 @@ export const prepareDiagnosticMetricItem = (webex: any, item: any) => {
332
369
  case 'client.media.tx.start':
333
370
  audioSetupDelay.joinRespTxStart = cdl.getAudioJoinRespTxStart();
334
371
  videoSetupDelay.joinRespTxStart = cdl.getVideoJoinRespTxStart();
372
+ break;
373
+
374
+ case 'client.lobby.exited':
375
+ joinTimes.stayLobbyTime = cdl.getStayLobbyTime();
376
+ break;
335
377
  }
336
378
 
337
379
  if (!isEmpty(joinTimes)) {
@@ -348,6 +390,11 @@ export const prepareDiagnosticMetricItem = (webex: any, item: any) => {
348
390
 
349
391
  item.eventPayload.origin = Object.assign(origin, item.eventPayload.origin);
350
392
 
393
+ // Mark call milestones in logs for easier filtering and analysis
394
+ if (eventName) {
395
+ webex.logger.log('Milestone,CallDiagnostic', eventName);
396
+ }
397
+
351
398
  webex.logger.log(
352
399
  `CallDiagnosticLatencies,prepareDiagnosticMetricItem: ${JSON.stringify({
353
400
  latencies: Object.fromEntries(cdl.latencyTimestamps),