@webex/internal-plugin-metrics 3.12.0-llmrefactor.1 → 3.12.0-llmrefactor.10

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 (63) hide show
  1. package/README.md +274 -0
  2. package/dist/call-diagnostic/call-diagnostic-metrics.js +52 -18
  3. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
  4. package/dist/call-diagnostic/config.js +3 -1
  5. package/dist/call-diagnostic/config.js.map +1 -1
  6. package/dist/config.js +7 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/index.js +10 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/metrics.js +118 -5
  11. package/dist/metrics.js.map +1 -1
  12. package/dist/metrics.types.js.map +1 -1
  13. package/dist/network-telemetry.js +610 -0
  14. package/dist/network-telemetry.js.map +1 -0
  15. package/dist/new-metrics.js +47 -11
  16. package/dist/new-metrics.js.map +1 -1
  17. package/dist/privacy-and-security-permission-enricher/constants.js +18 -0
  18. package/dist/privacy-and-security-permission-enricher/constants.js.map +1 -0
  19. package/dist/privacy-and-security-permission-enricher/index.js +143 -0
  20. package/dist/privacy-and-security-permission-enricher/index.js.map +1 -0
  21. package/dist/privacy-and-security-permission-enricher/types.js +7 -0
  22. package/dist/privacy-and-security-permission-enricher/types.js.map +1 -0
  23. package/dist/privacy-and-security-permission-enricher/utils.js +71 -0
  24. package/dist/privacy-and-security-permission-enricher/utils.js.map +1 -0
  25. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +9 -3
  26. package/dist/types/call-diagnostic/config.d.ts +2 -0
  27. package/dist/types/config.d.ts +8 -0
  28. package/dist/types/index.d.ts +2 -2
  29. package/dist/types/metrics.types.d.ts +3 -0
  30. package/dist/types/network-telemetry.d.ts +108 -0
  31. package/dist/types/new-metrics.d.ts +13 -1
  32. package/dist/types/privacy-and-security-permission-enricher/constants.d.ts +6 -0
  33. package/dist/types/privacy-and-security-permission-enricher/index.d.ts +40 -0
  34. package/dist/types/privacy-and-security-permission-enricher/types.d.ts +14 -0
  35. package/dist/types/privacy-and-security-permission-enricher/utils.d.ts +5 -0
  36. package/dist/types/unhandled-exception-telemetry/index.d.ts +60 -0
  37. package/dist/types/unhandled-exception-telemetry/utils.d.ts +6 -0
  38. package/dist/unhandled-exception-telemetry/index.js +330 -0
  39. package/dist/unhandled-exception-telemetry/index.js.map +1 -0
  40. package/dist/unhandled-exception-telemetry/utils.js +105 -0
  41. package/dist/unhandled-exception-telemetry/utils.js.map +1 -0
  42. package/package.json +9 -9
  43. package/src/call-diagnostic/call-diagnostic-metrics.ts +56 -20
  44. package/src/call-diagnostic/config.ts +2 -0
  45. package/src/config.js +7 -0
  46. package/src/index.ts +11 -0
  47. package/src/metrics.js +123 -5
  48. package/src/metrics.types.ts +9 -0
  49. package/src/network-telemetry.ts +757 -0
  50. package/src/new-metrics.ts +44 -2
  51. package/src/privacy-and-security-permission-enricher/constants.ts +33 -0
  52. package/src/privacy-and-security-permission-enricher/index.ts +142 -0
  53. package/src/privacy-and-security-permission-enricher/types.ts +21 -0
  54. package/src/privacy-and-security-permission-enricher/utils.ts +82 -0
  55. package/src/unhandled-exception-telemetry/index.ts +403 -0
  56. package/src/unhandled-exception-telemetry/utils.ts +101 -0
  57. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +211 -21
  58. package/test/unit/spec/metrics.js +14 -5
  59. package/test/unit/spec/network-telemetry.ts +610 -0
  60. package/test/unit/spec/new-metrics.ts +168 -52
  61. package/test/unit/spec/privacy-and-security-permission-enricher.ts +318 -0
  62. package/test/unit/spec/unhandled-exception-telemetry/utils.ts +109 -0
  63. package/test/unit/spec/unhandled-exception-telemetry.ts +688 -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;
@@ -385,6 +392,20 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
385
392
  throw new Error("ClientType and SubClientType can't be undefined");
386
393
  }
387
394
 
395
+ /**
396
+ * Returns the signed-in user's CI ID derived from credentials tokens.
397
+ * Used as a fallback when no device is registered.
398
+ * @returns the userId, or undefined if it can't be determined
399
+ */
400
+ private getUserIdFromCredentials(): string | undefined {
401
+ try {
402
+ // @ts-ignore
403
+ return this.webex.credentials.getUserId();
404
+ } catch {
405
+ return undefined;
406
+ }
407
+ }
408
+
388
409
  /**
389
410
  * Gather identifier details for call diagnostic payload.
390
411
  * @throws Error if initialization fails.
@@ -429,7 +450,6 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
429
450
  const {device} = this;
430
451
  const {installationId} = device?.config || {};
431
452
 
432
- identifiers.userId = device?.userId || preLoginId;
433
453
  identifiers.deviceId = device?.url;
434
454
  identifiers.orgId = device?.orgId;
435
455
  // @ts-ignore
@@ -440,6 +460,10 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
440
460
  }
441
461
  }
442
462
 
463
+ // Prefer the device's userId, but fall back to the signed-in user's ID from
464
+ // credentials when no device is registered, then to the pre-login ID.
465
+ identifiers.userId = this.device?.userId || this.getUserIdFromCredentials() || preLoginId;
466
+
443
467
  if (meeting?.locusInfo?.fullState) {
444
468
  identifiers.locusUrl = meeting.locusUrl;
445
469
  identifiers.locusId = meeting.locusUrl && meeting.locusUrl.split('/').pop();
@@ -702,7 +726,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
702
726
  * @param arg.clientErrorCode
703
727
  * @param arg.serviceErrorCode
704
728
  * @param arg.payloadOverrides
705
- * @param arg.httpStatusCode
729
+ * @param arg.httpCode
706
730
  * @returns
707
731
  */
708
732
  public getErrorPayloadForClientErrorCode({
@@ -711,14 +735,14 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
711
735
  serviceErrorName,
712
736
  rawErrorMessage,
713
737
  payloadOverrides,
714
- httpStatusCode,
738
+ httpCode,
715
739
  }: {
716
740
  clientErrorCode: number;
717
741
  serviceErrorCode: any;
718
742
  serviceErrorName?: any;
719
743
  rawErrorMessage?: string;
720
744
  payloadOverrides?: any;
721
- httpStatusCode?: number;
745
+ httpCode?: number;
722
746
  }): ClientEventError {
723
747
  let error: ClientEventError;
724
748
 
@@ -732,7 +756,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
732
756
  serviceErrorName ? {errorData: {errorName: serviceErrorName}} : {},
733
757
  {serviceErrorCode},
734
758
  {rawErrorMessage},
735
- httpStatusCode === undefined ? {} : {httpStatusCode},
759
+ httpCode === undefined ? {} : {httpCode},
736
760
  partialParsedError,
737
761
  payloadOverrides || {}
738
762
  );
@@ -901,7 +925,12 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
901
925
  }
902
926
 
903
927
  const rawErrorMessage = rawError.message;
904
- const httpStatusCode = rawError.statusCode;
928
+ const httpCode = rawError.statusCode;
929
+ const serviceErrorCode =
930
+ rawError?.error?.body?.errorCode ||
931
+ rawError?.body?.errorCode ||
932
+ rawError?.body?.code ||
933
+ rawError?.body?.reason?.reasonCode;
905
934
  let payload;
906
935
 
907
936
  if (rawError.name) {
@@ -911,7 +940,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
911
940
  clientErrorCode: BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP[rawError.name],
912
941
  serviceErrorName: rawError.name,
913
942
  rawErrorMessage,
914
- httpStatusCode,
943
+ httpCode,
915
944
  });
916
945
  }
917
946
  }
@@ -926,16 +955,10 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
926
955
  SDP_OFFER_CREATION_ERROR_MAP[causeType] || SDP_OFFER_CREATION_ERROR_MAP.GENERAL,
927
956
  serviceErrorName: rawError.name,
928
957
  rawErrorMessage,
929
- httpStatusCode,
958
+ httpCode,
930
959
  });
931
960
  }
932
961
 
933
- const serviceErrorCode =
934
- rawError?.error?.body?.errorCode ||
935
- rawError?.body?.errorCode ||
936
- rawError?.body?.code ||
937
- rawError?.body?.reason?.reasonCode;
938
-
939
962
  if (serviceErrorCode) {
940
963
  const clientErrorCode = SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP[serviceErrorCode];
941
964
  if (clientErrorCode && !payload) {
@@ -943,17 +966,30 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
943
966
  clientErrorCode,
944
967
  serviceErrorCode,
945
968
  rawErrorMessage,
946
- httpStatusCode,
969
+ httpCode,
947
970
  });
948
971
  }
972
+ }
949
973
 
974
+ const locusHttpClientErrorCode = LOCUS_HTTP_STATUS_TO_CLIENT_ERROR_CODE[httpCode];
975
+ const requestUrl = rawError.options?.uri || rawError.options?.url;
976
+ if (locusHttpClientErrorCode && !payload && requestUrl?.includes('locus')) {
977
+ payload = this.getErrorPayloadForClientErrorCode({
978
+ clientErrorCode: locusHttpClientErrorCode,
979
+ serviceErrorCode,
980
+ rawErrorMessage,
981
+ httpCode,
982
+ });
983
+ }
984
+
985
+ if (serviceErrorCode) {
950
986
  // by default, if it is locus error, return new locus err
951
987
  if (isLocusServiceErrorCode(serviceErrorCode) && !payload) {
952
988
  payload = this.getErrorPayloadForClientErrorCode({
953
989
  clientErrorCode: NEW_LOCUS_ERROR_CLIENT_CODE,
954
990
  serviceErrorCode,
955
991
  rawErrorMessage,
956
- httpStatusCode,
992
+ httpCode,
957
993
  });
958
994
  }
959
995
  }
@@ -963,7 +999,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
963
999
  clientErrorCode: MEETING_INFO_LOOKUP_ERROR_CLIENT_CODE,
964
1000
  serviceErrorCode,
965
1001
  rawErrorMessage,
966
- httpStatusCode,
1002
+ httpCode,
967
1003
  });
968
1004
  }
969
1005
 
@@ -973,7 +1009,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
973
1009
  serviceErrorCode,
974
1010
  payloadOverrides: rawError.payloadOverrides,
975
1011
  rawErrorMessage,
976
- httpStatusCode,
1012
+ httpCode,
977
1013
  });
978
1014
  }
979
1015
 
@@ -983,7 +1019,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
983
1019
  serviceErrorCode,
984
1020
  payloadOverrides: rawError.payloadOverrides,
985
1021
  rawErrorMessage,
986
- httpStatusCode,
1022
+ httpCode,
987
1023
  });
988
1024
  }
989
1025
 
@@ -995,7 +1031,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
995
1031
  serviceErrorName: rawError?.name,
996
1032
  payloadOverrides: rawError.payloadOverrides,
997
1033
  rawErrorMessage,
998
- httpStatusCode,
1034
+ httpCode,
999
1035
  });
1000
1036
  }
1001
1037
 
@@ -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;
package/src/config.js CHANGED
@@ -15,6 +15,13 @@ export default {
15
15
  },
16
16
  metrics: {
17
17
  appType: inBrowser ? 'browser' : 'nodejs',
18
+ unhandledExceptionTelemetry: {
19
+ enabled: false,
20
+ },
21
+ networkTelemetry: {
22
+ enabled: false,
23
+ intervalMs: 10 * 60 * 1_000,
24
+ },
18
25
  batcherWait: 500,
19
26
  batcherMaxCalls: 50,
20
27
  batcherMaxWait: 1500,
package/src/index.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
4
 
5
+ /* eslint-disable valid-jsdoc */
6
+
5
7
  import {registerInternalPlugin} from '@webex/webex-core';
6
8
 
7
9
  import Metrics from './metrics';
@@ -21,6 +23,7 @@ import {
21
23
  PreComputedLatencies,
22
24
  SubmitFeatureEvent,
23
25
  LocusSyncLatencyEventName,
26
+ PrivacyAndSecurityPermission,
24
27
  } from './metrics.types';
25
28
  import * as CALL_DIAGNOSTIC_CONFIG from './call-diagnostic/config';
26
29
  import * as CallDiagnosticUtils from './call-diagnostic/call-diagnostic-metrics.util';
@@ -34,6 +37,13 @@ import PreLoginMetrics from './prelogin-metrics';
34
37
 
35
38
  registerInternalPlugin('metrics', Metrics, {
36
39
  config,
40
+ /**
41
+ * Stops network telemetry before SDK logout.
42
+ * @returns
43
+ */
44
+ onBeforeLogout() {
45
+ return this.stopNetworkTelemetry();
46
+ },
37
47
  });
38
48
 
39
49
  registerInternalPlugin('newMetrics', NewMetrics, {
@@ -70,4 +80,5 @@ export type {
70
80
  PreComputedLatencies,
71
81
  SubmitFeatureEvent,
72
82
  LocusSyncLatencyEventName,
83
+ PrivacyAndSecurityPermission,
73
84
  };
package/src/metrics.js CHANGED
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable default-param-last */
2
+ /* eslint-disable valid-jsdoc */
2
3
 
3
4
  /*!
4
5
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
@@ -11,6 +12,7 @@ import {OS_NAME, OSMap, CLIENT_NAME} from './config';
11
12
  import Batcher from './batcher';
12
13
  import ClientMetricsBatcher from './client-metrics-batcher';
13
14
  import ClientMetricsPreloginBatcher from './client-metrics-prelogin-batcher';
15
+ import {createNetworkTelemetryCollector} from './network-telemetry';
14
16
 
15
17
  const {getOSName, getOSVersion, getBrowserName, getBrowserVersion} = BrowserDetection();
16
18
 
@@ -43,6 +45,119 @@ const Metrics = WebexPlugin.extend({
43
45
 
44
46
  namespace: 'Metrics',
45
47
 
48
+ /**
49
+ * Registers SDK request outcome monitoring.
50
+ * @param args
51
+ * @returns
52
+ */
53
+ initialize(...args) {
54
+ WebexPlugin.prototype.initialize.call(this, ...args);
55
+
56
+ // Metrics may also be constructed as a standalone plugin. Request events are
57
+ // available only when it is attached to a Webex instance.
58
+ if (!this.parent && !this.collection) {
59
+ return;
60
+ }
61
+
62
+ const initializeNetworkTelemetry = () => {
63
+ if (
64
+ this.networkTelemetry ||
65
+ this.webex?.config?.metrics?.networkTelemetry?.enabled !== true
66
+ ) {
67
+ return;
68
+ }
69
+
70
+ this.networkTelemetry = createNetworkTelemetryCollector({
71
+ intervalMs: this.webex.config.metrics.networkTelemetry.intervalMs,
72
+ submitMetric: this.submitNetworkTelemetryMetric.bind(this),
73
+ onSubmissionFailure: this.handleNetworkTelemetrySubmissionFailure.bind(this),
74
+ });
75
+ this.listenTo(this.webex, 'request:start', this.recordNetworkRequestStart);
76
+ this.listenTo(this.webex, 'request:success', this.recordNetworkRequestSuccess);
77
+ this.listenTo(this.webex, 'request:failure', this.recordNetworkRequestFailure);
78
+ };
79
+
80
+ // Ampersand initializes children before WebexCore.initialize() populates
81
+ // webex.config. Run immediately for already-configured hosts (including
82
+ // MockWebex), and listen for the real core's post-initialization notification.
83
+ this.listenToOnce(this.webex, 'change:config', initializeNetworkTelemetry);
84
+ initializeNetworkTelemetry();
85
+ },
86
+
87
+ /**
88
+ * Records an SDK request being sent in the current reporting window.
89
+ * @param options
90
+ * @returns
91
+ */
92
+ recordNetworkRequestStart(options) {
93
+ this.networkTelemetry.recordRequest(options);
94
+ },
95
+
96
+ /**
97
+ * Records a successful SDK request for the current reporting window.
98
+ * @param options
99
+ * @returns
100
+ */
101
+ recordNetworkRequestSuccess(options) {
102
+ this.networkTelemetry.recordResponse(options);
103
+ },
104
+
105
+ /**
106
+ * Records a failed SDK request for the current reporting window.
107
+ * @param options
108
+ * @param reason
109
+ * @returns
110
+ */
111
+ recordNetworkRequestFailure(options, reason) {
112
+ this.networkTelemetry.recordFailure(options, reason);
113
+ },
114
+
115
+ /**
116
+ * Submits the current network telemetry window immediately.
117
+ * @returns
118
+ */
119
+ flushNetworkTelemetry() {
120
+ if (!this.networkTelemetry) {
121
+ return Promise.resolve();
122
+ }
123
+
124
+ return this.networkTelemetry.flush();
125
+ },
126
+
127
+ /**
128
+ * Submits a completed network telemetry window through client metrics.
129
+ * @param name
130
+ * @param properties
131
+ * @returns
132
+ */
133
+ submitNetworkTelemetryMetric(name, properties) {
134
+ return this.submitClientMetrics(name, properties);
135
+ },
136
+
137
+ /**
138
+ * Logs a network telemetry submission failure without affecting SDK requests.
139
+ * @returns
140
+ */
141
+ handleNetworkTelemetrySubmissionFailure() {
142
+ this.webex.logger?.warn('metrics: failed to submit network request summary telemetry');
143
+ },
144
+
145
+ /**
146
+ * Stops network telemetry collection and submission.
147
+ * @returns
148
+ */
149
+ stopNetworkTelemetry() {
150
+ if (!this.networkTelemetry) {
151
+ return Promise.resolve();
152
+ }
153
+
154
+ this.stopListening(this.webex, 'request:start', this.recordNetworkRequestStart);
155
+ this.stopListening(this.webex, 'request:success', this.recordNetworkRequestSuccess);
156
+ this.stopListening(this.webex, 'request:failure', this.recordNetworkRequestFailure);
157
+
158
+ return this.networkTelemetry.stop();
159
+ },
160
+
46
161
  submit(key, value) {
47
162
  return this.batcher.request({key, ...value});
48
163
  },
@@ -60,17 +175,20 @@ const Metrics = WebexPlugin.extend({
60
175
  const payload = {metricName: eventName};
61
176
  // @ts-ignore
62
177
  const providedClientVersion = this.webex.meetings?.config?.metrics?.clientVersion;
178
+ const {appName, appVersion} = this.webex.config;
179
+ // Browser location is unavailable when the SDK runs in Node.js.
180
+ // eslint-disable-next-line no-undef
181
+ const browserLocation = typeof window !== 'undefined' ? window.location : undefined;
63
182
 
64
183
  payload.tags = {
65
184
  ...props.tags,
185
+ app_name: appName ?? 'unknown',
186
+ app_url: browserLocation?.origin || browserLocation?.hostname || 'non-browser',
187
+ app_version: appVersion ?? 'unknown',
66
188
  browser: getBrowserName(),
67
189
  os: getOSNameInternal(),
68
190
  appVersion: providedClientVersion,
69
-
70
- // Node does not like this so we need to check if it exists or not
71
- // eslint-disable-next-line no-undef
72
- domain:
73
- typeof window !== 'undefined' ? window.location.hostname || 'non-browser' : 'non-browser', // Check what else we could measure
191
+ domain: browserLocation?.hostname || 'non-browser',
74
192
  };
75
193
 
76
194
  payload.fields = {
@@ -23,6 +23,15 @@ export type ClientUserNameInput = NonNullable<RawClientEvent['userNameInput']>;
23
23
 
24
24
  export type ClientEmailInput = NonNullable<RawClientEvent['emailInput']>;
25
25
 
26
+ export type PrivacyAndSecurityPermission = NonNullable<
27
+ RawClientEvent['privacyAndSecurityPermission']
28
+ >;
29
+
30
+ export type PrivacyAndSecurityPermissionResource = keyof PrivacyAndSecurityPermission;
31
+
32
+ export type PrivacyAndSecurityPermissionState =
33
+ PrivacyAndSecurityPermission[PrivacyAndSecurityPermissionResource];
34
+
26
35
  export type BrowserLaunchMethodType = NonNullable<
27
36
  RawEvent['origin']['clientInfo']
28
37
  >['browserLaunchMethod'];