@webex/internal-plugin-metrics 3.8.1 → 3.9.0-multipleLLM.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 (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 +351 -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 +177 -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 +19 -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 +368 -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 +26 -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 +864 -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
@@ -13,9 +13,12 @@ import {
13
13
  } from '@webex/internal-plugin-metrics';
14
14
  import uuid from 'uuid';
15
15
  import {omit} from 'lodash';
16
- import { glob } from 'glob';
17
- import { expect } from 'chai';
18
- import { ClientEmailInput, ClientUserNameInput } from '@webex/internal-plugin-metrics/src/metrics.types';
16
+ import {glob} from 'glob';
17
+ import {expect} from 'chai';
18
+ import {
19
+ ClientEmailInput,
20
+ ClientUserNameInput,
21
+ } from '@webex/internal-plugin-metrics/src/metrics.types';
19
22
 
20
23
  //@ts-ignore
21
24
  global.window = {location: {hostname: 'whatever'}};
@@ -65,14 +68,14 @@ describe('internal-plugin-metrics', () => {
65
68
  ...fakeMeeting,
66
69
  id: '4',
67
70
  isoLocalClientMeetingJoinTime: 'testTimeString',
68
- }
71
+ };
69
72
  const fakeMeeting5 = {
70
73
  ...fakeMeeting,
71
74
  id: '5',
72
75
  correlationId: 'correlationId5',
73
76
  sessionCorrelationId: 'sessionCorrelationId5',
74
77
  userNameInput: 'test',
75
- emailInput: 'test@test.com'
78
+ emailInput: 'test@test.com',
76
79
  };
77
80
 
78
81
  const fakeMeetings = {
@@ -182,7 +185,7 @@ describe('internal-plugin-metrics', () => {
182
185
  publicNetworkPrefix: '1.1.1.1',
183
186
  localNetworkPrefix: '1.1.1.1',
184
187
  os: getOSNameInternal(),
185
- osVersion: getOSVersion(),
188
+ osVersion: getOSVersion() || 'unknown',
186
189
  subClientType: 'WEB_APP',
187
190
  },
188
191
  environment: 'meeting_evn',
@@ -215,7 +218,7 @@ describe('internal-plugin-metrics', () => {
215
218
  publicNetworkPrefix: '1.1.1.1',
216
219
  localNetworkPrefix: '1.1.1.1',
217
220
  os: getOSNameInternal(),
218
- osVersion: getOSVersion(),
221
+ osVersion: getOSVersion() || 'unknown',
219
222
  subClientType: 'WEB_APP',
220
223
  clientLaunchMethod: 'url-handler',
221
224
  },
@@ -250,7 +253,7 @@ describe('internal-plugin-metrics', () => {
250
253
  publicNetworkPrefix: '1.1.1.1',
251
254
  localNetworkPrefix: '1.1.1.1',
252
255
  os: getOSNameInternal(),
253
- osVersion: getOSVersion(),
256
+ osVersion: getOSVersion() || 'unknown',
254
257
  subClientType: 'WEB_APP',
255
258
  clientLaunchMethod: 'url-handler',
256
259
  },
@@ -261,6 +264,41 @@ describe('internal-plugin-metrics', () => {
261
264
  });
262
265
  });
263
266
 
267
+ it('should build origin correctly and vendorId can be passed in options', () => {
268
+ sinon.stub(CallDiagnosticUtils, 'anonymizeIPAddress').returns('1.1.1.1');
269
+
270
+ //@ts-ignore
271
+ const res = cd.getOrigin(
272
+ {
273
+ subClientType: 'WEB_APP',
274
+ clientType: 'TEAMS_CLIENT',
275
+ clientLaunchMethod: 'url-handler',
276
+ vendorId: 'GoogleMeet',
277
+ },
278
+ fakeMeeting.id
279
+ );
280
+
281
+ assert.deepEqual(res, {
282
+ clientInfo: {
283
+ browser: getBrowserName(),
284
+ browserVersion: getBrowserVersion(),
285
+ clientType: 'TEAMS_CLIENT',
286
+ clientVersion: 'webex-js-sdk/webex-version',
287
+ publicNetworkPrefix: '1.1.1.1',
288
+ localNetworkPrefix: '1.1.1.1',
289
+ os: getOSNameInternal(),
290
+ osVersion: getOSVersion() || 'unknown',
291
+ subClientType: 'WEB_APP',
292
+ clientLaunchMethod: 'url-handler',
293
+ vendorId: 'GoogleMeet',
294
+ },
295
+ environment: 'meeting_evn',
296
+ name: 'endpoint',
297
+ networkType: 'unknown',
298
+ userAgent,
299
+ });
300
+ });
301
+
264
302
  it('should build origin correctly with browserLaunchMethod', () => {
265
303
  sinon.stub(CallDiagnosticUtils, 'anonymizeIPAddress').returns('1.1.1.1');
266
304
 
@@ -285,7 +323,7 @@ describe('internal-plugin-metrics', () => {
285
323
  publicNetworkPrefix: '1.1.1.1',
286
324
  localNetworkPrefix: '1.1.1.1',
287
325
  os: getOSNameInternal(),
288
- osVersion: getOSVersion(),
326
+ osVersion: getOSVersion() || 'unknown',
289
327
  subClientType: 'WEB_APP',
290
328
  clientLaunchMethod: 'url-handler',
291
329
  browserLaunchMethod: 'thinclient',
@@ -313,7 +351,7 @@ describe('internal-plugin-metrics', () => {
313
351
  publicNetworkPrefix: '1.1.1.1',
314
352
  localNetworkPrefix: '1.1.1.1',
315
353
  os: getOSNameInternal(),
316
- osVersion: getOSVersion(),
354
+ osVersion: getOSVersion() || 'unknown',
317
355
  subClientType: 'WEB_APP',
318
356
  },
319
357
  name: 'endpoint',
@@ -342,7 +380,7 @@ describe('internal-plugin-metrics', () => {
342
380
  majorVersion: 43,
343
381
  minorVersion: 9,
344
382
  os: getOSNameInternal(),
345
- osVersion: getOSVersion(),
383
+ osVersion: getOSVersion() || 'unknown',
346
384
  subClientType: 'WEB_APP',
347
385
  },
348
386
  environment: 'meeting_evn',
@@ -365,7 +403,7 @@ describe('internal-plugin-metrics', () => {
365
403
  publicNetworkPrefix: '1.3.4.0',
366
404
  localNetworkPrefix: undefined,
367
405
  os: getOSNameInternal(),
368
- osVersion: getOSVersion(),
406
+ osVersion: getOSVersion() || 'unknown',
369
407
  subClientType: 'WEB_APP',
370
408
  },
371
409
  name: 'endpoint',
@@ -823,7 +861,147 @@ describe('internal-plugin-metrics', () => {
823
861
  meetingId: fakeMeeting.id,
824
862
  mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
825
863
  };
864
+ cd.setMercuryConnectedStatus(true);
865
+
866
+ cd.submitClientEvent({
867
+ name: 'client.alert.displayed',
868
+ options,
869
+ });
870
+
871
+ assert.called(getIdentifiersSpy);
872
+ assert.calledWith(getIdentifiersSpy, {
873
+ meeting: fakeMeeting,
874
+ mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
875
+ webexConferenceIdStr: undefined,
876
+ sessionCorrelationId: undefined,
877
+ globalMeetingId: undefined,
878
+ });
879
+ assert.notCalled(generateClientEventErrorPayloadSpy);
880
+ assert.calledWith(
881
+ prepareDiagnosticEventSpy,
882
+ {
883
+ canProceed: true,
884
+ eventData: {
885
+ webClientDomain: 'whatever',
886
+ isMercuryConnected: true,
887
+ },
888
+ identifiers: {
889
+ correlationId: 'correlationId',
890
+ deviceId: 'deviceUrl',
891
+ locusId: 'url',
892
+ locusStartTime: 'lastActive',
893
+ locusUrl: 'locus/url',
894
+ mediaAgentAlias: 'alias',
895
+ mediaAgentGroupId: '1',
896
+ orgId: 'orgId',
897
+ userId: 'userId',
898
+ },
899
+ loginType: 'login-ci',
900
+ name: 'client.alert.displayed',
901
+ userType: 'host',
902
+ isConvergedArchitectureEnabled: undefined,
903
+ webexSubServiceType: undefined,
904
+ webClientPreload: undefined,
905
+ },
906
+ options
907
+ );
908
+ assert.calledWith(submitToCallDiagnosticsSpy, {
909
+ event: {
910
+ canProceed: true,
911
+ eventData: {
912
+ webClientDomain: 'whatever',
913
+ isMercuryConnected: true,
914
+ },
915
+ identifiers: {
916
+ correlationId: 'correlationId',
917
+ deviceId: 'deviceUrl',
918
+ locusId: 'url',
919
+ locusStartTime: 'lastActive',
920
+ locusUrl: 'locus/url',
921
+ mediaAgentAlias: 'alias',
922
+ mediaAgentGroupId: '1',
923
+ orgId: 'orgId',
924
+ userId: 'userId',
925
+ },
926
+ loginType: 'login-ci',
927
+ name: 'client.alert.displayed',
928
+ userType: 'host',
929
+ isConvergedArchitectureEnabled: undefined,
930
+ webexSubServiceType: undefined,
931
+ webClientPreload: undefined,
932
+ },
933
+ eventId: 'my-fake-id',
934
+ origin: {
935
+ origin: 'fake-origin',
936
+ },
937
+ originTime: {
938
+ sent: 'not_defined_yet',
939
+ triggered: now.toISOString(),
940
+ },
941
+ senderCountryCode: 'UK',
942
+ version: 1,
943
+ });
944
+ assert.calledWith(validatorSpy, {
945
+ type: 'ce',
946
+ event: {
947
+ event: {
948
+ canProceed: true,
949
+ eventData: {
950
+ webClientDomain: 'whatever',
951
+ isMercuryConnected: true,
952
+ },
953
+ identifiers: {
954
+ correlationId: 'correlationId',
955
+ deviceId: 'deviceUrl',
956
+ locusId: 'url',
957
+ locusStartTime: 'lastActive',
958
+ locusUrl: 'locus/url',
959
+ mediaAgentAlias: 'alias',
960
+ mediaAgentGroupId: '1',
961
+ orgId: 'orgId',
962
+ userId: 'userId',
963
+ },
964
+ loginType: 'login-ci',
965
+ name: 'client.alert.displayed',
966
+ userType: 'host',
967
+ isConvergedArchitectureEnabled: undefined,
968
+ webexSubServiceType: undefined,
969
+ webClientPreload: undefined,
970
+ },
971
+ eventId: 'my-fake-id',
972
+ origin: {
973
+ origin: 'fake-origin',
974
+ },
975
+ originTime: {
976
+ sent: 'not_defined_yet',
977
+ triggered: now.toISOString(),
978
+ },
979
+ senderCountryCode: 'UK',
980
+ version: 1,
981
+ },
982
+ });
983
+
984
+ const webexLoggerLogCalls = webex.logger.log.getCalls();
985
+ assert.deepEqual(webexLoggerLogCalls[1].args, [
986
+ 'call-diagnostic-events -> ',
987
+ 'CallDiagnosticMetrics: @submitClientEvent. Submit Client Event CA event.',
988
+ `name: client.alert.displayed`,
989
+ ]);
990
+ });
826
991
 
992
+ it('should submit client event correctly when mercury is not connected', () => {
993
+ const prepareDiagnosticEventSpy = sinon.spy(cd, 'prepareDiagnosticEvent');
994
+ const submitToCallDiagnosticsSpy = sinon.spy(cd, 'submitToCallDiagnostics');
995
+ const generateClientEventErrorPayloadSpy = sinon.spy(cd, 'generateClientEventErrorPayload');
996
+ const getIdentifiersSpy = sinon.spy(cd, 'getIdentifiers');
997
+ const getSubServiceTypeSpy = sinon.spy(cd, 'getSubServiceType');
998
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
999
+ const validatorSpy = sinon.spy(cd, 'validator');
1000
+ const options = {
1001
+ meetingId: fakeMeeting.id,
1002
+ mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
1003
+ };
1004
+ cd.setMercuryConnectedStatus(false);
827
1005
  cd.submitClientEvent({
828
1006
  name: 'client.alert.displayed',
829
1007
  options,
@@ -844,6 +1022,7 @@ describe('internal-plugin-metrics', () => {
844
1022
  canProceed: true,
845
1023
  eventData: {
846
1024
  webClientDomain: 'whatever',
1025
+ isMercuryConnected: false,
847
1026
  },
848
1027
  identifiers: {
849
1028
  correlationId: 'correlationId',
@@ -870,6 +1049,7 @@ describe('internal-plugin-metrics', () => {
870
1049
  canProceed: true,
871
1050
  eventData: {
872
1051
  webClientDomain: 'whatever',
1052
+ isMercuryConnected: false,
873
1053
  },
874
1054
  identifiers: {
875
1055
  correlationId: 'correlationId',
@@ -907,6 +1087,7 @@ describe('internal-plugin-metrics', () => {
907
1087
  canProceed: true,
908
1088
  eventData: {
909
1089
  webClientDomain: 'whatever',
1090
+ isMercuryConnected: false,
910
1091
  },
911
1092
  identifiers: {
912
1093
  correlationId: 'correlationId',
@@ -959,7 +1140,7 @@ describe('internal-plugin-metrics', () => {
959
1140
  meetingId: fakeMeeting3.id,
960
1141
  mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
961
1142
  };
962
-
1143
+ cd.setMercuryConnectedStatus(true);
963
1144
  cd.submitClientEvent({
964
1145
  name: 'client.alert.displayed',
965
1146
  options,
@@ -980,6 +1161,7 @@ describe('internal-plugin-metrics', () => {
980
1161
  canProceed: true,
981
1162
  eventData: {
982
1163
  webClientDomain: 'whatever',
1164
+ isMercuryConnected: true,
983
1165
  },
984
1166
  identifiers: {
985
1167
  correlationId: 'correlationId3',
@@ -1007,6 +1189,7 @@ describe('internal-plugin-metrics', () => {
1007
1189
  canProceed: true,
1008
1190
  eventData: {
1009
1191
  webClientDomain: 'whatever',
1192
+ isMercuryConnected: true,
1010
1193
  },
1011
1194
  identifiers: {
1012
1195
  correlationId: 'correlationId3',
@@ -1045,6 +1228,7 @@ describe('internal-plugin-metrics', () => {
1045
1228
  canProceed: true,
1046
1229
  eventData: {
1047
1230
  webClientDomain: 'whatever',
1231
+ isMercuryConnected: true,
1048
1232
  },
1049
1233
  identifiers: {
1050
1234
  correlationId: 'correlationId3',
@@ -1097,7 +1281,7 @@ describe('internal-plugin-metrics', () => {
1097
1281
  meetingId: fakeMeeting4.id,
1098
1282
  mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
1099
1283
  };
1100
-
1284
+ cd.setMercuryConnectedStatus(true);
1101
1285
  cd.submitClientEvent({
1102
1286
  name: 'client.alert.displayed',
1103
1287
  options,
@@ -1119,6 +1303,7 @@ describe('internal-plugin-metrics', () => {
1119
1303
  canProceed: true,
1120
1304
  eventData: {
1121
1305
  webClientDomain: 'whatever',
1306
+ isMercuryConnected: true,
1122
1307
  },
1123
1308
  identifiers: {
1124
1309
  correlationId: 'correlationId',
@@ -1146,6 +1331,7 @@ describe('internal-plugin-metrics', () => {
1146
1331
  canProceed: true,
1147
1332
  eventData: {
1148
1333
  webClientDomain: 'whatever',
1334
+ isMercuryConnected: true,
1149
1335
  },
1150
1336
  identifiers: {
1151
1337
  correlationId: 'correlationId',
@@ -1184,6 +1370,7 @@ describe('internal-plugin-metrics', () => {
1184
1370
  canProceed: true,
1185
1371
  eventData: {
1186
1372
  webClientDomain: 'whatever',
1373
+ isMercuryConnected: true,
1187
1374
  },
1188
1375
  identifiers: {
1189
1376
  correlationId: 'correlationId',
@@ -1235,7 +1422,7 @@ describe('internal-plugin-metrics', () => {
1235
1422
  meetingId: fakeMeeting5.id,
1236
1423
  mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
1237
1424
  };
1238
-
1425
+ cd.setMercuryConnectedStatus(true);
1239
1426
  cd.submitClientEvent({
1240
1427
  name: 'client.alert.displayed',
1241
1428
  options,
@@ -1256,6 +1443,7 @@ describe('internal-plugin-metrics', () => {
1256
1443
  canProceed: true,
1257
1444
  eventData: {
1258
1445
  webClientDomain: 'whatever',
1446
+ isMercuryConnected: true,
1259
1447
  },
1260
1448
  identifiers: {
1261
1449
  correlationId: 'correlationId5',
@@ -1285,6 +1473,7 @@ describe('internal-plugin-metrics', () => {
1285
1473
  canProceed: true,
1286
1474
  eventData: {
1287
1475
  webClientDomain: 'whatever',
1476
+ isMercuryConnected: true,
1288
1477
  },
1289
1478
  identifiers: {
1290
1479
  correlationId: 'correlationId5',
@@ -1325,6 +1514,7 @@ describe('internal-plugin-metrics', () => {
1325
1514
  canProceed: true,
1326
1515
  eventData: {
1327
1516
  webClientDomain: 'whatever',
1517
+ isMercuryConnected: true,
1328
1518
  },
1329
1519
  identifiers: {
1330
1520
  correlationId: 'correlationId5',
@@ -1436,7 +1626,7 @@ describe('internal-plugin-metrics', () => {
1436
1626
  globalMeetingId: 'globalMeetingId1',
1437
1627
  sessionCorrelationId: 'sessionCorrelationId1',
1438
1628
  };
1439
-
1629
+ cd.setMercuryConnectedStatus(true);
1440
1630
  cd.submitClientEvent({
1441
1631
  name: 'client.alert.displayed',
1442
1632
  options,
@@ -1457,6 +1647,7 @@ describe('internal-plugin-metrics', () => {
1457
1647
  canProceed: true,
1458
1648
  eventData: {
1459
1649
  webClientDomain: 'whatever',
1650
+ isMercuryConnected: true,
1460
1651
  },
1461
1652
  identifiers: {
1462
1653
  correlationId: 'correlationId',
@@ -1470,7 +1661,7 @@ describe('internal-plugin-metrics', () => {
1470
1661
  },
1471
1662
  loginType: 'login-ci',
1472
1663
  name: 'client.alert.displayed',
1473
- webClientPreload: undefined
1664
+ webClientPreload: undefined,
1474
1665
  },
1475
1666
  options
1476
1667
  );
@@ -1479,6 +1670,7 @@ describe('internal-plugin-metrics', () => {
1479
1670
  canProceed: true,
1480
1671
  eventData: {
1481
1672
  webClientDomain: 'whatever',
1673
+ isMercuryConnected: true,
1482
1674
  },
1483
1675
  identifiers: {
1484
1676
  correlationId: 'correlationId',
@@ -1492,7 +1684,7 @@ describe('internal-plugin-metrics', () => {
1492
1684
  },
1493
1685
  loginType: 'login-ci',
1494
1686
  name: 'client.alert.displayed',
1495
- webClientPreload: undefined
1687
+ webClientPreload: undefined,
1496
1688
  },
1497
1689
  eventId: 'my-fake-id',
1498
1690
  origin: {
@@ -1524,7 +1716,7 @@ describe('internal-plugin-metrics', () => {
1524
1716
  const generateClientEventErrorPayloadSpy = sinon.spy(cd, 'generateClientEventErrorPayload');
1525
1717
  const getIdentifiersSpy = sinon.spy(cd, 'getIdentifiers');
1526
1718
  sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
1527
-
1719
+ cd.setMercuryConnectedStatus(true);
1528
1720
  const options = {
1529
1721
  correlationId: 'correlationId',
1530
1722
  webexConferenceIdStr: 'webexConferenceIdStr1',
@@ -1553,6 +1745,7 @@ describe('internal-plugin-metrics', () => {
1553
1745
  canProceed: true,
1554
1746
  eventData: {
1555
1747
  webClientDomain: 'whatever',
1748
+ isMercuryConnected: true,
1556
1749
  },
1557
1750
  identifiers: {
1558
1751
  correlationId: 'correlationId',
@@ -1592,7 +1785,7 @@ describe('internal-plugin-metrics', () => {
1592
1785
  webexConferenceIdStr: 'webexConferenceIdStr1',
1593
1786
  globalMeetingId: 'globalMeetingId1',
1594
1787
  },
1595
- eventData: {webClientDomain: 'whatever'},
1788
+ eventData: {webClientDomain: 'whatever', isMercuryConnected: true},
1596
1789
  loginType: 'login-ci',
1597
1790
  webClientPreload: undefined,
1598
1791
  },
@@ -1610,7 +1803,7 @@ describe('internal-plugin-metrics', () => {
1610
1803
  const generateClientEventErrorPayloadSpy = sinon.spy(cd, 'generateClientEventErrorPayload');
1611
1804
  const getIdentifiersSpy = sinon.spy(cd, 'getIdentifiers');
1612
1805
  sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
1613
-
1806
+ cd.setMercuryConnectedStatus(true);
1614
1807
  const options = {
1615
1808
  correlationId: 'correlationId',
1616
1809
  webexConferenceIdStr: 'webexConferenceIdStr1',
@@ -1641,6 +1834,7 @@ describe('internal-plugin-metrics', () => {
1641
1834
  canProceed: true,
1642
1835
  eventData: {
1643
1836
  webClientDomain: 'whatever',
1837
+ isMercuryConnected: true,
1644
1838
  },
1645
1839
  identifiers: {
1646
1840
  correlationId: 'correlationId',
@@ -1682,7 +1876,7 @@ describe('internal-plugin-metrics', () => {
1682
1876
  webexConferenceIdStr: 'webexConferenceIdStr1',
1683
1877
  globalMeetingId: 'globalMeetingId1',
1684
1878
  },
1685
- eventData: {webClientDomain: 'whatever'},
1879
+ eventData: {webClientDomain: 'whatever', isMercuryConnected: true},
1686
1880
  loginType: 'login-ci',
1687
1881
  userNameInput: 'current',
1688
1882
  emailInput: 'current',
@@ -1700,7 +1894,7 @@ describe('internal-plugin-metrics', () => {
1700
1894
  meetingId: fakeMeeting2.id,
1701
1895
  mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
1702
1896
  };
1703
-
1897
+ cd.setMercuryConnectedStatus(true);
1704
1898
  cd.submitClientEvent({
1705
1899
  name: 'client.alert.displayed',
1706
1900
  options,
@@ -1711,6 +1905,7 @@ describe('internal-plugin-metrics', () => {
1711
1905
  canProceed: true,
1712
1906
  eventData: {
1713
1907
  webClientDomain: 'whatever',
1908
+ isMercuryConnected: true,
1714
1909
  },
1715
1910
  identifiers: {
1716
1911
  correlationId: 'correlationId2',
@@ -1752,7 +1947,7 @@ describe('internal-plugin-metrics', () => {
1752
1947
  mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
1753
1948
  sessionCorrelationId: 'sessionCorrelationId1',
1754
1949
  };
1755
-
1950
+ cd.setMercuryConnectedStatus(true);
1756
1951
  cd.submitClientEvent({
1757
1952
  name: 'client.alert.displayed',
1758
1953
  options,
@@ -1763,6 +1958,7 @@ describe('internal-plugin-metrics', () => {
1763
1958
  canProceed: true,
1764
1959
  eventData: {
1765
1960
  webClientDomain: 'whatever',
1961
+ isMercuryConnected: true,
1766
1962
  },
1767
1963
  identifiers: {
1768
1964
  correlationId: 'correlationId2',
@@ -1811,7 +2007,7 @@ describe('internal-plugin-metrics', () => {
1811
2007
  globalMeetingId: 'globalMeetingId1',
1812
2008
  sessionCorrelationId: 'sessionCorrelationId1',
1813
2009
  };
1814
-
2010
+ cd.setMercuryConnectedStatus(true);
1815
2011
  cd.submitClientEvent({
1816
2012
  name: 'client.alert.displayed',
1817
2013
  options,
@@ -1824,6 +2020,7 @@ describe('internal-plugin-metrics', () => {
1824
2020
  canProceed: true,
1825
2021
  eventData: {
1826
2022
  webClientDomain: 'whatever',
2023
+ isMercuryConnected: true,
1827
2024
  },
1828
2025
  identifiers: {
1829
2026
  correlationId: 'correlationId',
@@ -1846,6 +2043,7 @@ describe('internal-plugin-metrics', () => {
1846
2043
  canProceed: true,
1847
2044
  eventData: {
1848
2045
  webClientDomain: 'whatever',
2046
+ isMercuryConnected: true,
1849
2047
  },
1850
2048
  identifiers: {
1851
2049
  correlationId: 'correlationId',
@@ -1889,7 +2087,7 @@ describe('internal-plugin-metrics', () => {
1889
2087
  },
1890
2088
  },
1891
2089
  };
1892
-
2090
+ cd.setMercuryConnectedStatus(true);
1893
2091
  cd.submitClientEvent({
1894
2092
  name: 'client.alert.displayed',
1895
2093
  options,
@@ -1900,6 +2098,7 @@ describe('internal-plugin-metrics', () => {
1900
2098
  canProceed: true,
1901
2099
  eventData: {
1902
2100
  webClientDomain: 'whatever',
2101
+ isMercuryConnected: true,
1903
2102
  },
1904
2103
  identifiers: {
1905
2104
  correlationId: 'correlationId',
@@ -1968,7 +2167,7 @@ describe('internal-plugin-metrics', () => {
1968
2167
  mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
1969
2168
  rawError: new Error('bad times'),
1970
2169
  };
1971
-
2170
+ cd.setMercuryConnectedStatus(true);
1972
2171
  cd.submitClientEvent({
1973
2172
  name: 'client.alert.displayed',
1974
2173
  options,
@@ -1979,6 +2178,7 @@ describe('internal-plugin-metrics', () => {
1979
2178
  canProceed: true,
1980
2179
  eventData: {
1981
2180
  webClientDomain: 'whatever',
2181
+ isMercuryConnected: true,
1982
2182
  },
1983
2183
  identifiers: {
1984
2184
  correlationId: 'correlationId',
@@ -2047,7 +2247,7 @@ describe('internal-plugin-metrics', () => {
2047
2247
  correlationId: 'correlationId',
2048
2248
  rawError: new Error('bad times'),
2049
2249
  };
2050
-
2250
+ cd.setMercuryConnectedStatus(true);
2051
2251
  cd.submitClientEvent({
2052
2252
  name: 'client.alert.displayed',
2053
2253
  options,
@@ -2058,6 +2258,7 @@ describe('internal-plugin-metrics', () => {
2058
2258
  canProceed: true,
2059
2259
  eventData: {
2060
2260
  webClientDomain: 'whatever',
2261
+ isMercuryConnected: true,
2061
2262
  },
2062
2263
  identifiers: {
2063
2264
  correlationId: 'correlationId',
@@ -2124,7 +2325,7 @@ describe('internal-plugin-metrics', () => {
2124
2325
  },
2125
2326
  },
2126
2327
  };
2127
-
2328
+ cd.setMercuryConnectedStatus(true);
2128
2329
  cd.submitClientEvent({
2129
2330
  name: 'client.alert.displayed',
2130
2331
  options,
@@ -2135,6 +2336,7 @@ describe('internal-plugin-metrics', () => {
2135
2336
  canProceed: true,
2136
2337
  eventData: {
2137
2338
  webClientDomain: 'whatever',
2339
+ isMercuryConnected: true,
2138
2340
  },
2139
2341
  identifiers: {
2140
2342
  correlationId: 'correlationId',
@@ -2194,7 +2396,7 @@ describe('internal-plugin-metrics', () => {
2194
2396
  meetingId: fakeMeeting.id,
2195
2397
  mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
2196
2398
  };
2197
-
2399
+ cd.setMercuryConnectedStatus(true);
2198
2400
  cd.submitClientEvent({
2199
2401
  name: 'client.alert.displayed',
2200
2402
  payload: {
@@ -2215,6 +2417,7 @@ describe('internal-plugin-metrics', () => {
2215
2417
  canProceed: true,
2216
2418
  eventData: {
2217
2419
  webClientDomain: 'whatever',
2420
+ isMercuryConnected: true,
2218
2421
  },
2219
2422
  identifiers: {
2220
2423
  correlationId: 'correlationId',
@@ -2263,7 +2466,7 @@ describe('internal-plugin-metrics', () => {
2263
2466
  );
2264
2467
  });
2265
2468
 
2266
- it('should send behavioral event if meetingId provided but meeting is undefined', () => {
2469
+ it('should record failure metric when meetingId is provided but meeting is undefined', () => {
2267
2470
  webex.meetings.getBasicMeetingInformation = sinon.stub().returns(undefined);
2268
2471
 
2269
2472
  cd.submitClientEvent({name: 'client.alert.displayed', options: {meetingId: 'meetingId'}});
@@ -2297,6 +2500,228 @@ describe('internal-plugin-metrics', () => {
2297
2500
  assert.calledWith(cd.submitToCallDiagnosticsPreLogin, testEvent);
2298
2501
  assert.notCalled(cd.submitToCallDiagnostics);
2299
2502
  });
2503
+
2504
+ describe('Limiting repeated events', () => {
2505
+ beforeEach(() => {
2506
+ cd.clearEventLimits();
2507
+ });
2508
+
2509
+ const createEventLimitRegex = (eventName: string, eventType: string) => {
2510
+ const escapedEventName = eventName.replace(/\./g, '\\.');
2511
+ return new RegExp(`Event limit reached for ${escapedEventName} for ${eventType}`);
2512
+ };
2513
+
2514
+ it('should always send events that are not in the limiting switch cases', () => {
2515
+ const options = {
2516
+ meetingId: fakeMeeting.id,
2517
+ };
2518
+ const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
2519
+
2520
+ const baselineCallCount = webex.logger.log.callCount;
2521
+ cd.submitClientEvent({
2522
+ name: 'client.alert.displayed',
2523
+ options,
2524
+ });
2525
+
2526
+ cd.submitClientEvent({
2527
+ name: 'client.alert.displayed',
2528
+ options,
2529
+ });
2530
+
2531
+ cd.submitClientEvent({
2532
+ name: 'client.alert.displayed',
2533
+ options,
2534
+ });
2535
+
2536
+ assert.calledThrice(submitToCallDiagnosticsStub);
2537
+ });
2538
+
2539
+ ([
2540
+ ['client.media.render.start'],
2541
+ ['client.media.render.stop'],
2542
+ ['client.media.rx.start'],
2543
+ ['client.media.rx.stop'],
2544
+ ['client.media.tx.start'],
2545
+ ['client.media.tx.stop']
2546
+ ] as const).forEach(([name]) => {
2547
+ it(`should only send ${name} once per mediaType`, () => {
2548
+ const options = {
2549
+ meetingId: fakeMeeting.id,
2550
+ };
2551
+ const payload = {
2552
+ mediaType: 'video' as const,
2553
+ };
2554
+ const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
2555
+
2556
+ const baselineCallCount = webex.logger.log.callCount;
2557
+ // Send first event
2558
+ cd.submitClientEvent({
2559
+ name,
2560
+ payload,
2561
+ options,
2562
+ });
2563
+
2564
+ assert.calledOnce(submitToCallDiagnosticsStub);
2565
+ submitToCallDiagnosticsStub.resetHistory();
2566
+
2567
+ // Send second event of same type
2568
+ cd.submitClientEvent({
2569
+ name,
2570
+ payload,
2571
+ options,
2572
+ });
2573
+
2574
+ assert.notCalled(submitToCallDiagnosticsStub);
2575
+ assert.calledWith(
2576
+ webex.logger.log,
2577
+ 'call-diagnostic-events -> ',
2578
+ sinon.match(createEventLimitRegex(name, 'mediaType video'))
2579
+ );
2580
+ webex.logger.log.resetHistory();
2581
+
2582
+ // Send third event of same type
2583
+ cd.submitClientEvent({
2584
+ name,
2585
+ payload,
2586
+ options,
2587
+ });
2588
+
2589
+ assert.notCalled(submitToCallDiagnosticsStub);
2590
+ assert.neverCalledWithMatch(webex.logger.log,
2591
+ 'call-diagnostic-events -> ',
2592
+ sinon.match(createEventLimitRegex(name, 'mediaType video'))
2593
+ );
2594
+
2595
+ // Send fourth event with a different mediaType
2596
+ cd.submitClientEvent({
2597
+ name,
2598
+ payload: {mediaType: 'audio'},
2599
+ options,
2600
+ });
2601
+
2602
+ assert.calledOnce(submitToCallDiagnosticsStub);
2603
+ });
2604
+
2605
+ it(`should handle share media type with shareInstanceId correctly for ${name}`, () => {
2606
+ const options = {
2607
+ meetingId: fakeMeeting.id,
2608
+ };
2609
+ const payload = {
2610
+ mediaType: 'share' as const,
2611
+ shareInstanceId: 'instance-1',
2612
+ };
2613
+ const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
2614
+
2615
+ const baselineCallCount = webex.logger.log.callCount;
2616
+ // Send first event
2617
+ cd.submitClientEvent({
2618
+ name,
2619
+ payload,
2620
+ options,
2621
+ });
2622
+
2623
+ // Send second event with same shareInstanceId
2624
+ cd.submitClientEvent({
2625
+ name,
2626
+ payload,
2627
+ options,
2628
+ });
2629
+
2630
+ // Send event with different shareInstanceId
2631
+ cd.submitClientEvent({
2632
+ name,
2633
+ payload: { ...payload, shareInstanceId: 'instance-2' },
2634
+ options,
2635
+ });
2636
+
2637
+ assert.calledTwice(submitToCallDiagnosticsStub);
2638
+ });
2639
+ });
2640
+
2641
+ ([
2642
+ ['client.roap-message.received'],
2643
+ ['client.roap-message.sent']
2644
+ ] as const).forEach(([name]) => {
2645
+ it(`should not send third event of same type and not log warning again for ${name}`, () => {
2646
+ const options = {
2647
+ meetingId: fakeMeeting.id,
2648
+ };
2649
+ const payload = {
2650
+ roap: {
2651
+ messageType: 'OFFER' as const,
2652
+ },
2653
+ };
2654
+ const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
2655
+
2656
+ // Clear any existing call history to get accurate counts
2657
+ webex.logger.log.resetHistory();
2658
+
2659
+ // Send first event
2660
+ cd.submitClientEvent({
2661
+ name,
2662
+ payload,
2663
+ options,
2664
+ });
2665
+
2666
+ assert.calledOnce(submitToCallDiagnosticsStub);
2667
+ submitToCallDiagnosticsStub.resetHistory();
2668
+
2669
+ // Send second event (should trigger warning)
2670
+ cd.submitClientEvent({
2671
+ name,
2672
+ payload,
2673
+ options,
2674
+ });
2675
+
2676
+ assert.notCalled(submitToCallDiagnosticsStub);
2677
+ assert.calledWith(
2678
+ webex.logger.log,
2679
+ 'call-diagnostic-events -> ',
2680
+ sinon.match(createEventLimitRegex(name, 'ROAP type OFFER'))
2681
+ );
2682
+ webex.logger.log.resetHistory();
2683
+
2684
+ cd.submitClientEvent({
2685
+ name,
2686
+ payload,
2687
+ options,
2688
+ });
2689
+
2690
+ assert.notCalled(submitToCallDiagnosticsStub);
2691
+ assert.neverCalledWithMatch(
2692
+ webex.logger.log,
2693
+ 'call-diagnostic-events -> ',
2694
+ sinon.match(createEventLimitRegex(name, 'ROAP type OFFER'))
2695
+ );
2696
+ });
2697
+
2698
+ it(`should handle roap.type instead of roap.messageType for ${name}`, () => {
2699
+ const options = {
2700
+ meetingId: fakeMeeting.id,
2701
+ };
2702
+ const payload = {
2703
+ roap: {
2704
+ type: 'ANSWER' as const,
2705
+ },
2706
+ };
2707
+ const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
2708
+
2709
+ cd.submitClientEvent({
2710
+ name,
2711
+ payload,
2712
+ options,
2713
+ });
2714
+
2715
+ cd.submitClientEvent({
2716
+ name,
2717
+ payload,
2718
+ options,
2719
+ });
2720
+
2721
+ assert.calledOnce(submitToCallDiagnosticsStub);
2722
+ });
2723
+ });
2724
+ });
2300
2725
  });
2301
2726
 
2302
2727
  describe('#submitToCallDiagnostics', () => {
@@ -2371,7 +2796,7 @@ describe('internal-plugin-metrics', () => {
2371
2796
  applicationSoftwareType: 'webex-js-sdk',
2372
2797
  applicationSoftwareVersion: 'webex-version',
2373
2798
  mediaEngineSoftwareType: 'browser',
2374
- mediaEngineSoftwareVersion: getOSVersion(),
2799
+ mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
2375
2800
  startTime: now.toISOString(),
2376
2801
  },
2377
2802
  },
@@ -2410,7 +2835,7 @@ describe('internal-plugin-metrics', () => {
2410
2835
  applicationSoftwareType: 'webex-js-sdk',
2411
2836
  applicationSoftwareVersion: 'webex-version',
2412
2837
  mediaEngineSoftwareType: 'browser',
2413
- mediaEngineSoftwareVersion: getOSVersion(),
2838
+ mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
2414
2839
  startTime: now.toISOString(),
2415
2840
  },
2416
2841
  },
@@ -2447,7 +2872,7 @@ describe('internal-plugin-metrics', () => {
2447
2872
  applicationSoftwareType: 'webex-js-sdk',
2448
2873
  applicationSoftwareVersion: 'webex-version',
2449
2874
  mediaEngineSoftwareType: 'browser',
2450
- mediaEngineSoftwareVersion: getOSVersion(),
2875
+ mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
2451
2876
  startTime: now.toISOString(),
2452
2877
  },
2453
2878
  },
@@ -2574,7 +2999,7 @@ describe('internal-plugin-metrics', () => {
2574
2999
  name: 'other',
2575
3000
  rawErrorMessage: 'bad times',
2576
3001
  errorDescription: 'UnknownError',
2577
- }
3002
+ };
2578
3003
 
2579
3004
  const [res, cached] = cd.generateClientEventErrorPayload(error);
2580
3005
  assert.isFalse(cached);
@@ -2903,7 +3328,10 @@ describe('internal-plugin-metrics', () => {
2903
3328
  });
2904
3329
 
2905
3330
  it('should return unknown error otherwise', () => {
2906
- const [res, cached] = cd.generateClientEventErrorPayload({something: 'new', message: 'bad times'});
3331
+ const [res, cached] = cd.generateClientEventErrorPayload({
3332
+ something: 'new',
3333
+ message: 'bad times',
3334
+ });
2907
3335
  assert.deepEqual(res, {
2908
3336
  category: 'other',
2909
3337
  errorDescription: 'UnknownError',
@@ -3251,7 +3679,7 @@ describe('internal-plugin-metrics', () => {
3251
3679
  meetingId: fakeMeeting.id,
3252
3680
  preLoginId,
3253
3681
  };
3254
-
3682
+ cd.setMercuryConnectedStatus(true);
3255
3683
  const triggered = new Date();
3256
3684
  const fetchOptions = await cd.buildClientEventFetchRequestOptions({
3257
3685
  name: 'client.exit.app',
@@ -3267,6 +3695,7 @@ describe('internal-plugin-metrics', () => {
3267
3695
  canProceed: false,
3268
3696
  eventData: {
3269
3697
  webClientDomain: 'whatever',
3698
+ isMercuryConnected: true,
3270
3699
  },
3271
3700
  identifiers: {
3272
3701
  correlationId: 'correlationId',
@@ -3294,7 +3723,7 @@ describe('internal-plugin-metrics', () => {
3294
3723
  localNetworkPrefix: '192.168.1.80',
3295
3724
  publicNetworkPrefix: '1.3.4.0',
3296
3725
  os: getOSNameInternal() || 'unknown',
3297
- osVersion: getOSVersion(),
3726
+ osVersion: getOSVersion() || 'unknown',
3298
3727
  subClientType: 'WEB_APP',
3299
3728
  },
3300
3729
  environment: 'meeting_evn',
@@ -3573,7 +4002,7 @@ describe('internal-plugin-metrics', () => {
3573
4002
 
3574
4003
  const overrides = {
3575
4004
  correlationId: 'newCorrelationId',
3576
- }
4005
+ };
3577
4006
 
3578
4007
  cd.submitClientEvent({
3579
4008
  name: 'client.alert.displayed',
@@ -3635,5 +4064,401 @@ describe('internal-plugin-metrics', () => {
3635
4064
  assert.notCalled(submitClientEventSpy);
3636
4065
  });
3637
4066
  });
4067
+
4068
+ describe('#submitFeatureEvent', () => {
4069
+ it('should submit feature event successfully with meetingId', () => {
4070
+ const prepareDiagnosticEventSpy = sinon.spy(cd, 'prepareDiagnosticEvent');
4071
+ const submitToCallFeaturesSpy = sinon.spy(cd, 'submitToCallFeatures');
4072
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
4073
+
4074
+ const options = {
4075
+ meetingId: fakeMeeting.id,
4076
+ };
4077
+ cd.setMercuryConnectedStatus(true);
4078
+
4079
+ cd.submitFeatureEvent({
4080
+ name: 'client.feature.meeting.summary',
4081
+ payload: {
4082
+ meetingSummaryInfo: {
4083
+ featureName: 'syncSystemMuteStatus',
4084
+ featureActions: [{
4085
+ actionName: 'syncMeetingMicUnmuteStatusToSystem',
4086
+ actionId: '14200',
4087
+ isInitialValue: false,
4088
+ clickCount: '1'
4089
+ }]
4090
+ },
4091
+ },
4092
+ options,
4093
+ });
4094
+
4095
+ assert.calledWith(
4096
+ prepareDiagnosticEventSpy,
4097
+ {
4098
+ name: 'client.feature.meeting.summary',
4099
+ canProceed: true,
4100
+ identifiers: {
4101
+ correlationId: 'correlationId',
4102
+ userId: 'userId',
4103
+ deviceId: 'deviceUrl',
4104
+ orgId: 'orgId',
4105
+ locusUrl: 'locus/url',
4106
+ locusId: 'url',
4107
+ locusStartTime: 'lastActive',
4108
+ },
4109
+ eventData: { webClientDomain: 'whatever'},
4110
+ userType: 'host',
4111
+ loginType: 'login-ci',
4112
+ isConvergedArchitectureEnabled: undefined,
4113
+ webexSubServiceType: undefined,
4114
+ webClientPreload: undefined,
4115
+ meetingSummaryInfo: {
4116
+ featureName: 'syncSystemMuteStatus',
4117
+ featureActions: [{
4118
+ actionName: 'syncMeetingMicUnmuteStatusToSystem',
4119
+ actionId: '14200',
4120
+ isInitialValue: false,
4121
+ clickCount: '1'
4122
+ }]
4123
+ },
4124
+ key: "UcfFeatureUsage",
4125
+ },
4126
+ options
4127
+ );
4128
+
4129
+ assert.calledWith(submitToCallFeaturesSpy, {
4130
+ eventId: 'my-fake-id',
4131
+ version: 1,
4132
+ origin: {
4133
+ origin: 'fake-origin',
4134
+ },
4135
+ event: {
4136
+ canProceed: true,
4137
+ eventData: { webClientDomain: 'whatever'},
4138
+ identifiers: {
4139
+ correlationId: 'correlationId',
4140
+ deviceId: 'deviceUrl',
4141
+ locusId: 'url',
4142
+ locusStartTime: 'lastActive',
4143
+ locusUrl: 'locus/url',
4144
+ orgId: 'orgId',
4145
+ userId: 'userId',
4146
+ },
4147
+ loginType: 'login-ci',
4148
+ name: 'client.feature.meeting.summary',
4149
+ userType: 'host',
4150
+ isConvergedArchitectureEnabled: undefined,
4151
+ webexSubServiceType: undefined,
4152
+ webClientPreload: undefined,
4153
+ meetingSummaryInfo: {
4154
+ featureName: 'syncSystemMuteStatus',
4155
+ featureActions: [{
4156
+ actionName: 'syncMeetingMicUnmuteStatusToSystem',
4157
+ actionId: '14200',
4158
+ isInitialValue: false,
4159
+ clickCount: '1'
4160
+ }]
4161
+ },
4162
+ key: "UcfFeatureUsage",
4163
+ },
4164
+ originTime: {
4165
+ sent: 'not_defined_yet',
4166
+ triggered: now.toISOString(),
4167
+ },
4168
+ senderCountryCode: 'UK',
4169
+ });
4170
+
4171
+ const webexLoggerLogCalls = webex.logger.log.getCalls();
4172
+ assert.deepEqual(webexLoggerLogCalls[1].args, [
4173
+ 'call-diagnostic-events-feature -> ',
4174
+ 'CallFeatureMetrics: @submitFeatureEvent. Submit Client Feature Event CA event.',
4175
+ `name: client.feature.meeting.summary`,
4176
+ ]);
4177
+ });
4178
+ });
4179
+
4180
+ describe('#submitDelayedClientFeatureEvents', () => {
4181
+ it('does not call submitFeatureEvent if there were no delayed events', () => {
4182
+ const submitFeatureEventSpy = sinon.spy(cd, 'submitFeatureEvent');
4183
+
4184
+ cd.submitDelayedClientFeatureEvents();
4185
+
4186
+ assert.notCalled(submitFeatureEventSpy);
4187
+ });
4188
+
4189
+ it('calls submitFeatureEvent for every delayed event and clears delayedClientFeatureEvents array', () => {
4190
+ const submitFeatureEventSpy = sinon.spy(cd, 'submitFeatureEvent');
4191
+ const submitToCallFeaturesSpy = sinon.spy(cd, 'submitToCallFeatures');
4192
+
4193
+ const options = {
4194
+ meetingId: 'meetingId',
4195
+ };
4196
+
4197
+ cd.submitFeatureEvent({
4198
+ name: 'client.feature.meeting.summary',
4199
+ options,
4200
+ payload: {
4201
+ meetingSummaryInfo: {
4202
+ featureName: 'syncSystemMuteStatus',
4203
+ featureActions: [{
4204
+ actionName: 'syncMeetingMicUnmuteStatusToSystem',
4205
+ actionId: '14200',
4206
+ isInitialValue: false,
4207
+ clickCount: '1'
4208
+ }]
4209
+ },
4210
+ },
4211
+ delaySubmitEvent: true,
4212
+ });
4213
+
4214
+ cd.submitFeatureEvent({
4215
+ name: 'client.feature.meeting.summary',
4216
+ options,
4217
+ payload: {
4218
+ meetingSummaryInfo: {
4219
+ featureName: 'syncSystemVideoStatus',
4220
+ featureActions: [{
4221
+ actionName: 'syncMeetingVideoUnmuteStatusToSystem',
4222
+ actionId: '13400',
4223
+ isInitialValue: false,
4224
+ clickCount: '1'
4225
+ }]
4226
+ },
4227
+ },
4228
+ delaySubmitEvent: true,
4229
+ });
4230
+
4231
+ assert.notCalled(submitToCallFeaturesSpy);
4232
+ assert.calledTwice(submitFeatureEventSpy);
4233
+ submitFeatureEventSpy.resetHistory();
4234
+
4235
+ cd.submitDelayedClientFeatureEvents();
4236
+
4237
+ assert.calledTwice(submitFeatureEventSpy);
4238
+ assert.calledWith(submitFeatureEventSpy.firstCall, {
4239
+ name: 'client.feature.meeting.summary',
4240
+ payload: {
4241
+ meetingSummaryInfo: {
4242
+ featureName: 'syncSystemMuteStatus',
4243
+ featureActions: [{
4244
+ actionName: 'syncMeetingMicUnmuteStatusToSystem',
4245
+ actionId: '14200',
4246
+ isInitialValue: false,
4247
+ clickCount: '1'
4248
+ }]
4249
+ },
4250
+ },
4251
+ options: {
4252
+ meetingId: 'meetingId',
4253
+ triggeredTime: now.toISOString(),
4254
+ },
4255
+ });
4256
+ assert.calledWith(submitFeatureEventSpy.secondCall, {
4257
+ name: 'client.feature.meeting.summary',
4258
+ payload: {
4259
+ meetingSummaryInfo: {
4260
+ featureName: 'syncSystemVideoStatus',
4261
+ featureActions: [{
4262
+ actionName: 'syncMeetingVideoUnmuteStatusToSystem',
4263
+ actionId: '13400',
4264
+ isInitialValue: false,
4265
+ clickCount: '1'
4266
+ }]
4267
+ },
4268
+ },
4269
+ options: {
4270
+ meetingId: 'meetingId',
4271
+ triggeredTime: now.toISOString(),
4272
+ },
4273
+ });
4274
+ submitFeatureEventSpy.resetHistory();
4275
+
4276
+ cd.submitDelayedClientFeatureEvents();
4277
+
4278
+ // should not call submitFeatureEventSpy again if delayedClientFeatureEvents was cleared
4279
+ assert.notCalled(submitFeatureEventSpy);
4280
+ });
4281
+ });
4282
+
4283
+ describe('#clearEventLimitsForCorrelationId', () => {
4284
+ beforeEach(() => {
4285
+ cd.clearEventLimits();
4286
+ });
4287
+
4288
+ it('should clear event limits for specific correlationId only', () => {
4289
+ // Use the actual correlationIds from our fakeMeeting fixtures
4290
+ const correlationId1 = fakeMeeting.correlationId; // e.g. 'correlationId1'
4291
+ const correlationId2 = fakeMeeting2.correlationId; // e.g. 'correlationId2'
4292
+ const options1 = { meetingId: fakeMeeting.id };
4293
+ const options2 = { meetingId: fakeMeeting2.id };
4294
+ const payload = { mediaType: 'video' as const };
4295
+
4296
+ // Set up events for both correlations to trigger limits
4297
+ cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options1 });
4298
+ cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options2 });
4299
+ cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options1 });
4300
+ cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options2 });
4301
+ assert.isTrue(cd.eventLimitTracker.size > 0);
4302
+ assert.isTrue(cd.eventLimitWarningsLogged.size > 0);
4303
+
4304
+ // Clear limits for only correlationId1 (present)
4305
+ cd.clearEventLimitsForCorrelationId(correlationId1);
4306
+
4307
+ const remainingTrackerKeys = Array.from(cd.eventLimitTracker.keys());
4308
+ const remainingWarningKeys = Array.from(cd.eventLimitWarningsLogged.keys());
4309
+
4310
+ // Should have no keys with correlationId1
4311
+ assert.isFalse(remainingTrackerKeys.some(key => key.split(':')[1] === correlationId1));
4312
+ assert.isFalse(remainingWarningKeys.some(key => key.split(':')[1] === correlationId1));
4313
+
4314
+ // Should still have keys with correlationId2
4315
+ assert.isTrue(remainingTrackerKeys.some(key => key.split(':')[1] === correlationId2));
4316
+ assert.isTrue(remainingWarningKeys.some(key => key.split(':')[1] === correlationId2));
4317
+ });
4318
+
4319
+ it('should handle empty correlationId gracefully', () => {
4320
+ const options = { meetingId: fakeMeeting.id };
4321
+ const payload = { mediaType: 'video' as const };
4322
+
4323
+ // Set up some tracking data
4324
+ cd.submitClientEvent({
4325
+ name: 'client.media.render.start',
4326
+ payload,
4327
+ options,
4328
+ });
4329
+
4330
+ cd.submitClientEvent({
4331
+ name: 'client.media.render.start',
4332
+ payload,
4333
+ options,
4334
+ });
4335
+
4336
+ const initialTrackerSize = cd.eventLimitTracker.size;
4337
+ const initialWarningsSize = cd.eventLimitWarningsLogged.size;
4338
+
4339
+ // Should not clear anything for empty correlationId
4340
+ cd.clearEventLimitsForCorrelationId('');
4341
+ cd.clearEventLimitsForCorrelationId(null as any);
4342
+ cd.clearEventLimitsForCorrelationId(undefined as any);
4343
+
4344
+ assert.equal(cd.eventLimitTracker.size, initialTrackerSize);
4345
+ assert.equal(cd.eventLimitWarningsLogged.size, initialWarningsSize);
4346
+ });
4347
+
4348
+ it('should handle non-existent correlationId gracefully', () => {
4349
+ const options = { meetingId: fakeMeeting.id };
4350
+ const payload = { mediaType: 'video' as const };
4351
+
4352
+ // Set up some tracking data
4353
+ cd.submitClientEvent({
4354
+ name: 'client.media.render.start',
4355
+ payload,
4356
+ options,
4357
+ });
4358
+
4359
+ const initialTrackerSize = cd.eventLimitTracker.size;
4360
+ const initialWarningsSize = cd.eventLimitWarningsLogged.size;
4361
+
4362
+ // Should not clear anything for non-existent correlationId
4363
+ cd.clearEventLimitsForCorrelationId('nonExistentCorrelationId');
4364
+
4365
+ assert.equal(cd.eventLimitTracker.size, initialTrackerSize);
4366
+ assert.equal(cd.eventLimitWarningsLogged.size, initialWarningsSize);
4367
+ });
4368
+
4369
+ it('should clear multiple event types for the same correlationId', () => {
4370
+ const correlationId = fakeMeeting.correlationId;
4371
+ const options = { meetingId: fakeMeeting.id };
4372
+ const videoPayload = { mediaType: 'video' as const };
4373
+ const audioPayload = { mediaType: 'audio' as const };
4374
+ const roapPayload = { roap: { messageType: 'OFFER' as const } };
4375
+
4376
+ // Set up multiple event types for the same correlation
4377
+ cd.submitClientEvent({
4378
+ name: 'client.media.render.start',
4379
+ payload: videoPayload,
4380
+ options,
4381
+ });
4382
+
4383
+ cd.submitClientEvent({
4384
+ name: 'client.media.render.start',
4385
+ payload: audioPayload,
4386
+ options,
4387
+ });
4388
+
4389
+ cd.submitClientEvent({
4390
+ name: 'client.roap-message.sent',
4391
+ payload: roapPayload,
4392
+ options,
4393
+ });
4394
+
4395
+ // Trigger limits
4396
+ cd.submitClientEvent({
4397
+ name: 'client.media.render.start',
4398
+ payload: videoPayload,
4399
+ options,
4400
+ });
4401
+
4402
+ cd.submitClientEvent({
4403
+ name: 'client.media.render.start',
4404
+ payload: audioPayload,
4405
+ options,
4406
+ });
4407
+
4408
+ cd.submitClientEvent({
4409
+ name: 'client.roap-message.sent',
4410
+ payload: roapPayload,
4411
+ options,
4412
+ });
4413
+
4414
+ assert.isTrue(cd.eventLimitTracker.size > 0);
4415
+ assert.isTrue(cd.eventLimitWarningsLogged.size > 0);
4416
+
4417
+ // Clear all limits for this correlationId
4418
+ cd.clearEventLimitsForCorrelationId(correlationId);
4419
+
4420
+ // Should clear all tracking data for this correlationId
4421
+ assert.equal(cd.eventLimitTracker.size, 0);
4422
+ assert.equal(cd.eventLimitWarningsLogged.size, 0);
4423
+ });
4424
+
4425
+ it('should allow events to be sent again after clearing limits for correlationId', () => {
4426
+ const correlationId = fakeMeeting.correlationId;
4427
+ const options = { meetingId: fakeMeeting.id };
4428
+ const payload = { mediaType: 'video' as const };
4429
+ const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
4430
+
4431
+ // Send first event (should succeed)
4432
+ cd.submitClientEvent({
4433
+ name: 'client.media.render.start',
4434
+ payload,
4435
+ options,
4436
+ });
4437
+
4438
+ assert.calledOnce(submitToCallDiagnosticsStub);
4439
+ submitToCallDiagnosticsStub.resetHistory();
4440
+
4441
+ // Send second event (should be blocked)
4442
+ cd.submitClientEvent({
4443
+ name: 'client.media.render.start',
4444
+ payload,
4445
+ options,
4446
+ });
4447
+
4448
+ assert.notCalled(submitToCallDiagnosticsStub);
4449
+
4450
+ // Clear limits for this correlationId
4451
+ cd.clearEventLimitsForCorrelationId(correlationId);
4452
+
4453
+ // Send event again (should succeed after clearing)
4454
+ cd.submitClientEvent({
4455
+ name: 'client.media.render.start',
4456
+ payload,
4457
+ options,
4458
+ });
4459
+
4460
+ assert.calledOnce(submitToCallDiagnosticsStub);
4461
+ });
4462
+ });
3638
4463
  });
3639
4464
  });