@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.
- package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +92 -14
- package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
- package/dist/call-diagnostic/call-diagnostic-metrics.js +348 -48
- package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
- package/dist/call-diagnostic/call-diagnostic-metrics.util.js +21 -0
- package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
- package/dist/call-diagnostic/config.js +3 -1
- package/dist/call-diagnostic/config.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics.js +1 -1
- package/dist/metrics.types.js.map +1 -1
- package/dist/new-metrics.js +43 -1
- package/dist/new-metrics.js.map +1 -1
- package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +23 -1
- package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +176 -10
- package/dist/types/call-diagnostic/config.d.ts +2 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/metrics.types.d.ts +18 -7
- package/dist/types/new-metrics.d.ts +19 -2
- package/package.json +11 -12
- package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +104 -14
- package/src/call-diagnostic/call-diagnostic-metrics.ts +363 -25
- package/src/call-diagnostic/call-diagnostic-metrics.util.ts +20 -0
- package/src/call-diagnostic/config.ts +3 -0
- package/src/index.ts +2 -0
- package/src/metrics.types.ts +25 -6
- package/src/new-metrics.ts +52 -1
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +20 -1
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +255 -0
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +829 -39
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +6 -0
- package/test/unit/spec/new-metrics.ts +67 -2
- package/test/unit/spec/prelogin-metrics-batcher.ts +72 -3
- package/dist/call-diagnostic-events-batcher.js +0 -60
- 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(
|
|
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
|
|
156
|
-
return
|
|
159
|
+
if (typeof start !== 'number' || typeof end !== 'number') {
|
|
160
|
+
return undefined;
|
|
157
161
|
}
|
|
158
162
|
|
|
159
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
684
|
-
* @param
|
|
685
|
-
* @param
|
|
686
|
-
* @param
|
|
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
|
|
931
|
+
private createCommonEventObjectInMeeting({
|
|
690
932
|
name,
|
|
691
933
|
options,
|
|
692
|
-
|
|
934
|
+
eventType = 'client',
|
|
693
935
|
}: {
|
|
694
|
-
name:
|
|
936
|
+
name: string;
|
|
695
937
|
options?: SubmitClientEventOptions;
|
|
696
|
-
|
|
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
|
|
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(
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
|
735
|
-
const
|
|
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
|
-
|
|
1006
|
+
// @ts-ignore
|
|
1007
|
+
commonEventObject.joinFlowVersion = joinFlowVersion;
|
|
761
1008
|
}
|
|
762
1009
|
const meetingJoinedTime = meeting.isoLocalClientMeetingJoinTime;
|
|
763
1010
|
if (meetingJoinedTime) {
|
|
764
|
-
|
|
1011
|
+
// @ts-ignore
|
|
1012
|
+
commonEventObject.meetingJoinedTime = meetingJoinedTime;
|
|
765
1013
|
}
|
|
766
1014
|
|
|
767
1015
|
if (options.meetingJoinPhase) {
|
|
768
|
-
|
|
1016
|
+
// @ts-ignore
|
|
1017
|
+
commonEventObject.meetingJoinPhase = options.meetingJoinPhase;
|
|
769
1018
|
}
|
|
770
1019
|
|
|
771
|
-
return
|
|
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
|