@webex/internal-plugin-metrics 3.12.0-webex-services-ready.4 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.repo-context.json +466 -0
  2. package/.sdd/manifest.json +626 -0
  3. package/AGENTS.md +208 -0
  4. package/README.md +274 -0
  5. package/dist/call-diagnostic/call-diagnostic-metrics.js +155 -94
  6. package/dist/call-diagnostic/call-diagnostic-metrics.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/config.js +7 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/index.js +10 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/metrics.js +118 -5
  14. package/dist/metrics.js.map +1 -1
  15. package/dist/metrics.types.js.map +1 -1
  16. package/dist/network-telemetry.js +610 -0
  17. package/dist/network-telemetry.js.map +1 -0
  18. package/dist/new-metrics.js +47 -11
  19. package/dist/new-metrics.js.map +1 -1
  20. package/dist/privacy-and-security-permission-enricher/constants.js +18 -0
  21. package/dist/privacy-and-security-permission-enricher/constants.js.map +1 -0
  22. package/dist/privacy-and-security-permission-enricher/index.js +143 -0
  23. package/dist/privacy-and-security-permission-enricher/index.js.map +1 -0
  24. package/dist/privacy-and-security-permission-enricher/types.js +7 -0
  25. package/dist/privacy-and-security-permission-enricher/types.js.map +1 -0
  26. package/dist/privacy-and-security-permission-enricher/utils.js +71 -0
  27. package/dist/privacy-and-security-permission-enricher/utils.js.map +1 -0
  28. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +12 -3
  29. package/dist/types/call-diagnostic/config.d.ts +2 -0
  30. package/dist/types/config.d.ts +8 -0
  31. package/dist/types/index.d.ts +2 -2
  32. package/dist/types/metrics.types.d.ts +7 -3
  33. package/dist/types/network-telemetry.d.ts +108 -0
  34. package/dist/types/new-metrics.d.ts +13 -1
  35. package/dist/types/privacy-and-security-permission-enricher/constants.d.ts +6 -0
  36. package/dist/types/privacy-and-security-permission-enricher/index.d.ts +40 -0
  37. package/dist/types/privacy-and-security-permission-enricher/types.d.ts +14 -0
  38. package/dist/types/privacy-and-security-permission-enricher/utils.d.ts +5 -0
  39. package/dist/types/unhandled-exception-telemetry/index.d.ts +60 -0
  40. package/dist/types/unhandled-exception-telemetry/utils.d.ts +6 -0
  41. package/dist/unhandled-exception-telemetry/index.js +331 -0
  42. package/dist/unhandled-exception-telemetry/index.js.map +1 -0
  43. package/dist/unhandled-exception-telemetry/utils.js +105 -0
  44. package/dist/unhandled-exception-telemetry/utils.js.map +1 -0
  45. package/docs/adr/index.md +27 -0
  46. package/docs/architecture.md +437 -0
  47. package/docs/getting-started.md +131 -0
  48. package/docs/index.md +52 -0
  49. package/docs/specs/README.md +88 -0
  50. package/package.json +11 -11
  51. package/schemas/repo-context.schema.json +796 -0
  52. package/src/call-diagnostic/call-diagnostic-metrics.ts +100 -30
  53. package/src/call-diagnostic/config.ts +2 -0
  54. package/src/call-diagnostic/docs/README.md +535 -0
  55. package/src/config.js +7 -0
  56. package/src/docs/README.md +703 -0
  57. package/src/index.ts +11 -0
  58. package/src/metrics.js +123 -5
  59. package/src/metrics.types.ts +13 -3
  60. package/src/network-telemetry.ts +757 -0
  61. package/src/new-metrics.ts +44 -2
  62. package/src/privacy-and-security-permission-enricher/constants.ts +33 -0
  63. package/src/privacy-and-security-permission-enricher/docs/README.md +356 -0
  64. package/src/privacy-and-security-permission-enricher/index.ts +142 -0
  65. package/src/privacy-and-security-permission-enricher/types.ts +21 -0
  66. package/src/privacy-and-security-permission-enricher/utils.ts +82 -0
  67. package/src/rtcMetrics/docs/README.md +447 -0
  68. package/src/unhandled-exception-telemetry/docs/README.md +536 -0
  69. package/src/unhandled-exception-telemetry/index.ts +404 -0
  70. package/src/unhandled-exception-telemetry/utils.ts +101 -0
  71. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +485 -21
  72. package/test/unit/spec/metrics.js +14 -5
  73. package/test/unit/spec/network-telemetry.ts +610 -0
  74. package/test/unit/spec/new-metrics.ts +190 -48
  75. package/test/unit/spec/privacy-and-security-permission-enricher.ts +318 -0
  76. package/test/unit/spec/unhandled-exception-telemetry/utils.ts +109 -0
  77. package/test/unit/spec/unhandled-exception-telemetry.ts +689 -0
@@ -64,10 +64,17 @@ import {
64
64
  SDP_OFFER_CREATION_ERROR_MAP,
65
65
  CALL_FEATURE_LOG_IDENTIFIER,
66
66
  CALL_FEATURE_EVENT_FAILED_TO_SEND,
67
+ LOCUS_RATE_LIMITED_OUTGOING_CLIENT_CODE,
68
+ LOCUS_UNAVAILABLE_CLIENT_CODE,
67
69
  } from './config';
68
70
 
69
71
  const {getOSVersion, getBrowserName, getBrowserVersion} = BrowserDetection();
70
72
 
73
+ const LOCUS_HTTP_STATUS_TO_CLIENT_ERROR_CODE: Partial<Record<number, number>> = {
74
+ 429: LOCUS_RATE_LIMITED_OUTGOING_CLIENT_CODE,
75
+ 503: LOCUS_UNAVAILABLE_CLIENT_CODE,
76
+ };
77
+
71
78
  type GetOriginOptions = {
72
79
  clientType: ClientType;
73
80
  subClientType: SubClientType;
@@ -87,6 +94,7 @@ type GetIdentifiersOptions = {
87
94
  preLoginId?: string;
88
95
  globalMeetingId?: string;
89
96
  webexConferenceIdStr?: string;
97
+ locusCluster?: string;
90
98
  };
91
99
 
92
100
  /**
@@ -292,6 +300,18 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
292
300
  versionMetadata = extractVersionMetadata(providedClientVersion);
293
301
  }
294
302
 
303
+ // Browser details and the support verdict can all be supplied by the host client, which is able
304
+ // to resolve wrapper browsers (Electron, WebOS) to the engine that actually determines
305
+ // capability. The browser fields fall back to the SDK's own detection when not supplied, the
306
+ // same way clientVersion falls back to the SDK version.
307
+ const {
308
+ browser: providedBrowser,
309
+ browserVersion: providedBrowserVersion,
310
+ isSupportedBrowserFamily,
311
+ isOutdatedBrowserVersion,
312
+ // @ts-ignore
313
+ } = this.webex.meetings?.config?.metrics ?? {};
314
+
295
315
  if (!this.hasLoggedBrowserSerial) {
296
316
  this.logger.log(
297
317
  CALL_DIAGNOSTIC_LOG_IDENTIFIER,
@@ -332,14 +352,16 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
332
352
  osVersion: getOSVersion() || 'unknown',
333
353
  subClientType: options?.subClientType || defaultSubClientType,
334
354
  os: getOSNameInternal(),
335
- browser: getBrowserName(),
336
- browserVersion: getBrowserVersion(),
355
+ browser: providedBrowser || getBrowserName(),
356
+ browserVersion: providedBrowserVersion || getBrowserVersion(),
357
+ ...(isSupportedBrowserFamily === undefined ? {} : {isSupportedBrowserFamily}),
358
+ ...(isOutdatedBrowserVersion === undefined ? {} : {isOutdatedBrowserVersion}),
337
359
  },
338
360
  };
339
361
 
340
362
  if (meetingId) {
341
363
  // @ts-ignore
342
- const meeting = this.webex.meetings.getBasicMeetingInformation(meetingId);
364
+ const meeting = this.webex.meetings?.getBasicMeetingInformation(meetingId);
343
365
  if (meeting?.environment) {
344
366
  origin.environment = meeting.environment;
345
367
  }
@@ -371,6 +393,20 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
371
393
  throw new Error("ClientType and SubClientType can't be undefined");
372
394
  }
373
395
 
396
+ /**
397
+ * Returns the signed-in user's CI ID derived from credentials tokens.
398
+ * Used as a fallback when no device is registered.
399
+ * @returns the userId, or undefined if it can't be determined
400
+ */
401
+ private getUserIdFromCredentials(): string | undefined {
402
+ try {
403
+ // @ts-ignore
404
+ return this.webex.credentials.getUserId();
405
+ } catch {
406
+ return undefined;
407
+ }
408
+ }
409
+
374
410
  /**
375
411
  * Gather identifier details for call diagnostic payload.
376
412
  * @throws Error if initialization fails.
@@ -385,6 +421,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
385
421
  globalMeetingId,
386
422
  preLoginId,
387
423
  sessionCorrelationId,
424
+ locusCluster,
388
425
  } = options;
389
426
  const identifiers: Event['event']['identifiers'] = {
390
427
  correlationId: 'unknown', // concerned with setting this to unknown. This will fail diagnostic events parsing because it's not a uuid pattern
@@ -415,7 +452,6 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
415
452
  const {device} = this;
416
453
  const {installationId} = device?.config || {};
417
454
 
418
- identifiers.userId = device?.userId || preLoginId;
419
455
  identifiers.deviceId = device?.url;
420
456
  identifiers.orgId = device?.orgId;
421
457
  // @ts-ignore
@@ -426,6 +462,10 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
426
462
  }
427
463
  }
428
464
 
465
+ // Prefer the device's userId, but fall back to the signed-in user's ID from
466
+ // credentials when no device is registered, then to the pre-login ID.
467
+ identifiers.userId = this.device?.userId || this.getUserIdFromCredentials() || preLoginId;
468
+
429
469
  if (meeting?.locusInfo?.fullState) {
430
470
  identifiers.locusUrl = meeting.locusUrl;
431
471
  identifiers.locusId = meeting.locusUrl && meeting.locusUrl.split('/').pop();
@@ -448,6 +488,10 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
448
488
  identifiers.webexSiteName = meeting.meetingInfo?.siteName;
449
489
  }
450
490
 
491
+ if (meeting?.meetingInfo?.locusClusterUrl) {
492
+ identifiers.locusCluster = meeting.meetingInfo.locusClusterUrl;
493
+ }
494
+
451
495
  if (mediaConnections) {
452
496
  identifiers.mediaAgentAlias = mediaConnections?.[0]?.mediaAgentAlias;
453
497
  identifiers.mediaAgentGroupId = mediaConnections?.[0]?.mediaAgentGroupId;
@@ -461,6 +505,10 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
461
505
  identifiers.globalMeetingId = globalMeetingId;
462
506
  }
463
507
 
508
+ if (!identifiers?.locusCluster && locusCluster) {
509
+ identifiers.locusCluster = locusCluster;
510
+ }
511
+
464
512
  if (identifiers.correlationId === undefined) {
465
513
  throw new Error('Identifiers initialization failed.');
466
514
  }
@@ -688,7 +736,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
688
736
  * @param arg.clientErrorCode
689
737
  * @param arg.serviceErrorCode
690
738
  * @param arg.payloadOverrides
691
- * @param arg.httpStatusCode
739
+ * @param arg.httpCode
692
740
  * @returns
693
741
  */
694
742
  public getErrorPayloadForClientErrorCode({
@@ -697,14 +745,14 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
697
745
  serviceErrorName,
698
746
  rawErrorMessage,
699
747
  payloadOverrides,
700
- httpStatusCode,
748
+ httpCode,
701
749
  }: {
702
750
  clientErrorCode: number;
703
751
  serviceErrorCode: any;
704
752
  serviceErrorName?: any;
705
753
  rawErrorMessage?: string;
706
754
  payloadOverrides?: any;
707
- httpStatusCode?: number;
755
+ httpCode?: number;
708
756
  }): ClientEventError {
709
757
  let error: ClientEventError;
710
758
 
@@ -718,7 +766,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
718
766
  serviceErrorName ? {errorData: {errorName: serviceErrorName}} : {},
719
767
  {serviceErrorCode},
720
768
  {rawErrorMessage},
721
- httpStatusCode === undefined ? {} : {httpStatusCode},
769
+ httpCode === undefined ? {} : {httpCode},
722
770
  partialParsedError,
723
771
  payloadOverrides || {}
724
772
  );
@@ -887,7 +935,12 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
887
935
  }
888
936
 
889
937
  const rawErrorMessage = rawError.message;
890
- const httpStatusCode = rawError.statusCode;
938
+ const httpCode = rawError.statusCode;
939
+ const serviceErrorCode =
940
+ rawError?.error?.body?.errorCode ||
941
+ rawError?.body?.errorCode ||
942
+ rawError?.body?.code ||
943
+ rawError?.body?.reason?.reasonCode;
891
944
  let payload;
892
945
 
893
946
  if (rawError.name) {
@@ -897,7 +950,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
897
950
  clientErrorCode: BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP[rawError.name],
898
951
  serviceErrorName: rawError.name,
899
952
  rawErrorMessage,
900
- httpStatusCode,
953
+ httpCode,
901
954
  });
902
955
  }
903
956
  }
@@ -912,16 +965,10 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
912
965
  SDP_OFFER_CREATION_ERROR_MAP[causeType] || SDP_OFFER_CREATION_ERROR_MAP.GENERAL,
913
966
  serviceErrorName: rawError.name,
914
967
  rawErrorMessage,
915
- httpStatusCode,
968
+ httpCode,
916
969
  });
917
970
  }
918
971
 
919
- const serviceErrorCode =
920
- rawError?.error?.body?.errorCode ||
921
- rawError?.body?.errorCode ||
922
- rawError?.body?.code ||
923
- rawError?.body?.reason?.reasonCode;
924
-
925
972
  if (serviceErrorCode) {
926
973
  const clientErrorCode = SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP[serviceErrorCode];
927
974
  if (clientErrorCode && !payload) {
@@ -929,17 +976,30 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
929
976
  clientErrorCode,
930
977
  serviceErrorCode,
931
978
  rawErrorMessage,
932
- httpStatusCode,
979
+ httpCode,
933
980
  });
934
981
  }
982
+ }
983
+
984
+ const locusHttpClientErrorCode = LOCUS_HTTP_STATUS_TO_CLIENT_ERROR_CODE[httpCode];
985
+ const requestUrl = rawError.options?.uri || rawError.options?.url;
986
+ if (locusHttpClientErrorCode && !payload && requestUrl?.includes('locus')) {
987
+ payload = this.getErrorPayloadForClientErrorCode({
988
+ clientErrorCode: locusHttpClientErrorCode,
989
+ serviceErrorCode,
990
+ rawErrorMessage,
991
+ httpCode,
992
+ });
993
+ }
935
994
 
995
+ if (serviceErrorCode) {
936
996
  // by default, if it is locus error, return new locus err
937
997
  if (isLocusServiceErrorCode(serviceErrorCode) && !payload) {
938
998
  payload = this.getErrorPayloadForClientErrorCode({
939
999
  clientErrorCode: NEW_LOCUS_ERROR_CLIENT_CODE,
940
1000
  serviceErrorCode,
941
1001
  rawErrorMessage,
942
- httpStatusCode,
1002
+ httpCode,
943
1003
  });
944
1004
  }
945
1005
  }
@@ -949,7 +1009,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
949
1009
  clientErrorCode: MEETING_INFO_LOOKUP_ERROR_CLIENT_CODE,
950
1010
  serviceErrorCode,
951
1011
  rawErrorMessage,
952
- httpStatusCode,
1012
+ httpCode,
953
1013
  });
954
1014
  }
955
1015
 
@@ -959,7 +1019,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
959
1019
  serviceErrorCode,
960
1020
  payloadOverrides: rawError.payloadOverrides,
961
1021
  rawErrorMessage,
962
- httpStatusCode,
1022
+ httpCode,
963
1023
  });
964
1024
  }
965
1025
 
@@ -969,7 +1029,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
969
1029
  serviceErrorCode,
970
1030
  payloadOverrides: rawError.payloadOverrides,
971
1031
  rawErrorMessage,
972
- httpStatusCode,
1032
+ httpCode,
973
1033
  });
974
1034
  }
975
1035
 
@@ -981,7 +1041,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
981
1041
  serviceErrorName: rawError?.name,
982
1042
  payloadOverrides: rawError.payloadOverrides,
983
1043
  rawErrorMessage,
984
- httpStatusCode,
1044
+ httpCode,
985
1045
  });
986
1046
  }
987
1047
 
@@ -1013,6 +1073,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1013
1073
  globalMeetingId,
1014
1074
  webexConferenceIdStr,
1015
1075
  sessionCorrelationId,
1076
+ locusCluster,
1016
1077
  } = options;
1017
1078
 
1018
1079
  // @ts-ignore
@@ -1046,6 +1107,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1046
1107
  webexConferenceIdStr,
1047
1108
  globalMeetingId,
1048
1109
  sessionCorrelationId,
1110
+ locusCluster,
1049
1111
  });
1050
1112
 
1051
1113
  // create common event object structure
@@ -1086,9 +1148,11 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1086
1148
  commonEventObject.meetingJoinedTime = meetingJoinedTime;
1087
1149
  }
1088
1150
 
1089
- if (options.meetingJoinPhase) {
1151
+ const meetingJoinPhase =
1152
+ options.meetingJoinPhase ?? meeting.callStateForMetrics?.meetingJoinPhase;
1153
+ if (meetingJoinPhase) {
1090
1154
  // @ts-ignore
1091
- commonEventObject.meetingJoinPhase = options.meetingJoinPhase;
1155
+ commonEventObject.meetingJoinPhase = meetingJoinPhase;
1092
1156
  }
1093
1157
 
1094
1158
  return commonEventObject;
@@ -1169,8 +1233,14 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1169
1233
  options?: SubmitClientEventOptions;
1170
1234
  errors?: ClientEventPayloadError;
1171
1235
  }) {
1172
- const {correlationId, globalMeetingId, webexConferenceIdStr, preLoginId, sessionCorrelationId} =
1173
- options;
1236
+ const {
1237
+ correlationId,
1238
+ globalMeetingId,
1239
+ webexConferenceIdStr,
1240
+ preLoginId,
1241
+ sessionCorrelationId,
1242
+ locusCluster,
1243
+ } = options;
1174
1244
 
1175
1245
  // grab identifiers
1176
1246
  const identifiers = this.getIdentifiers({
@@ -1179,6 +1249,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1179
1249
  preLoginId,
1180
1250
  globalMeetingId,
1181
1251
  webexConferenceIdStr,
1252
+ locusCluster,
1182
1253
  });
1183
1254
 
1184
1255
  // create client event object
@@ -1203,9 +1274,8 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1203
1274
  clientEventObject.joinFlowVersion = options.joinFlowVersion;
1204
1275
  }
1205
1276
 
1206
- if (options.meetingJoinPhase) {
1207
- clientEventObject.meetingJoinPhase = options.meetingJoinPhase;
1208
- }
1277
+ // No meeting exists yet for pre-meeting events, so the join phase is pre-join by construction
1278
+ clientEventObject.meetingJoinPhase = options.meetingJoinPhase ?? 'pre-join';
1209
1279
 
1210
1280
  if (options.userNameInput) {
1211
1281
  clientEventObject.userNameInput = options.userNameInput;
@@ -8,6 +8,8 @@ export const CALL_DIAGNOSTIC_LOG_IDENTIFIER = 'call-diagnostic-events -> ';
8
8
  export const CALL_FEATURE_LOG_IDENTIFIER = 'call-diagnostic-events-feature -> ';
9
9
 
10
10
  export const AUTHENTICATION_FAILED_CODE = 1010;
11
+ export const LOCUS_RATE_LIMITED_OUTGOING_CLIENT_CODE = 1002;
12
+ export const LOCUS_UNAVAILABLE_CLIENT_CODE = 1003;
11
13
  export const NETWORK_ERROR = 1026;
12
14
  export const NEW_LOCUS_ERROR_CLIENT_CODE = 4008;
13
15
  export const MEETING_INFO_LOOKUP_ERROR_CLIENT_CODE = 4100;