@webex/internal-plugin-metrics 3.12.0-next.4 → 3.12.0-next.41

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 (65) hide show
  1. package/dist/automated-user.js +19 -0
  2. package/dist/automated-user.js.map +1 -0
  3. package/dist/batcher.js +3 -0
  4. package/dist/batcher.js.map +1 -1
  5. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +638 -65
  6. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
  7. package/dist/call-diagnostic/call-diagnostic-metrics.js +167 -76
  8. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
  9. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +5 -0
  10. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
  11. package/dist/call-diagnostic/config.js +16 -3
  12. package/dist/call-diagnostic/config.js.map +1 -1
  13. package/dist/config.js +1 -0
  14. package/dist/config.js.map +1 -1
  15. package/dist/index.js +15 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/metrics.js +1 -1
  18. package/dist/metrics.types.js +4 -0
  19. package/dist/metrics.types.js.map +1 -1
  20. package/dist/new-metrics.js +60 -17
  21. package/dist/new-metrics.js.map +1 -1
  22. package/dist/privacy-and-security-permission-enricher/constants.js +18 -0
  23. package/dist/privacy-and-security-permission-enricher/constants.js.map +1 -0
  24. package/dist/privacy-and-security-permission-enricher/index.js +143 -0
  25. package/dist/privacy-and-security-permission-enricher/index.js.map +1 -0
  26. package/dist/privacy-and-security-permission-enricher/types.js +7 -0
  27. package/dist/privacy-and-security-permission-enricher/types.js.map +1 -0
  28. package/dist/privacy-and-security-permission-enricher/utils.js +71 -0
  29. package/dist/privacy-and-security-permission-enricher/utils.js.map +1 -0
  30. package/dist/types/automated-user.d.ts +2 -0
  31. package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +208 -5
  32. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +67 -14
  33. package/dist/types/call-diagnostic/config.d.ts +4 -0
  34. package/dist/types/config.d.ts +1 -0
  35. package/dist/types/index.d.ts +5 -3
  36. package/dist/types/metrics.types.d.ts +7 -2
  37. package/dist/types/new-metrics.d.ts +17 -1
  38. package/dist/types/privacy-and-security-permission-enricher/constants.d.ts +6 -0
  39. package/dist/types/privacy-and-security-permission-enricher/index.d.ts +40 -0
  40. package/dist/types/privacy-and-security-permission-enricher/types.d.ts +14 -0
  41. package/dist/types/privacy-and-security-permission-enricher/utils.d.ts +5 -0
  42. package/package.json +12 -11
  43. package/src/automated-user.ts +16 -0
  44. package/src/batcher.js +4 -0
  45. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +800 -73
  46. package/src/call-diagnostic/call-diagnostic-metrics.ts +101 -15
  47. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +5 -0
  48. package/src/call-diagnostic/config.ts +14 -0
  49. package/src/config.js +1 -0
  50. package/src/index.ts +7 -0
  51. package/src/metrics.types.ts +25 -3
  52. package/src/new-metrics.ts +53 -6
  53. package/src/privacy-and-security-permission-enricher/constants.ts +33 -0
  54. package/src/privacy-and-security-permission-enricher/index.ts +142 -0
  55. package/src/privacy-and-security-permission-enricher/types.ts +21 -0
  56. package/src/privacy-and-security-permission-enricher/utils.ts +82 -0
  57. package/test/unit/spec/automated-user.ts +43 -0
  58. package/test/unit/spec/batcher.js +43 -0
  59. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +14 -2
  60. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +1434 -303
  61. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +852 -159
  62. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +27 -0
  63. package/test/unit/spec/new-metrics.ts +183 -50
  64. package/test/unit/spec/prelogin-metrics-batcher.ts +71 -36
  65. package/test/unit/spec/privacy-and-security-permission-enricher.ts +318 -0
@@ -6,6 +6,7 @@ import uuid from 'uuid';
6
6
  import {merge} from 'lodash';
7
7
  import {StatelessWebexPlugin} from '@webex/webex-core';
8
8
  import {getOSNameInternal} from '../metrics';
9
+ import * as AutomatedUserUtils from '../automated-user';
9
10
 
10
11
  import {
11
12
  anonymizeIPAddress,
@@ -99,7 +100,12 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
99
100
  // @ts-ignore
100
101
  private preLoginMetricsBatcher: PreLoginMetricsBatcher;
101
102
 
102
- private logger: any; // to avoid adding @ts-ignore everywhere
103
+ // lazy getter to avoid @ts-ignore on every call site
104
+ private get logger(): any {
105
+ // @ts-ignore
106
+ return this.webex.logger;
107
+ }
108
+
103
109
  private hasLoggedBrowserSerial: boolean;
104
110
  private device: any;
105
111
  private delayedClientEvents: DelayedClientEvent[] = [];
@@ -108,6 +114,8 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
108
114
  private isMercuryConnected = false;
109
115
  private eventLimitTracker: Map<string, number> = new Map();
110
116
  private eventLimitWarningsLogged: Set<string> = new Set();
117
+ private isTelemetryOptOutManual = false;
118
+ private isTelemetryOptOutAutomatic = false;
111
119
 
112
120
  // the default validator before piping an event to the batcher
113
121
  // this function can be overridden by the user
@@ -124,8 +132,6 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
124
132
  constructor(...args) {
125
133
  super(...args);
126
134
  // @ts-ignore
127
- this.logger = this.webex.logger;
128
- // @ts-ignore
129
135
  this.callDiagnosticEventsBatcher = new CallDiagnosticEventsBatcher({}, {parent: this.webex});
130
136
  // @ts-ignore
131
137
  this.preLoginMetricsBatcher = new PreLoginMetricsBatcher({}, {parent: this.webex});
@@ -145,6 +151,58 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
145
151
  return null;
146
152
  }
147
153
 
154
+ /**
155
+ * Returns the user activation state reported from the browser's navigator.userActivation API
156
+ * @returns object with hasBeenActive and isActive booleans, or undefined if unavailable
157
+ */
158
+ getUserActivation(): {hasBeenActive: boolean; isActive: boolean} | undefined {
159
+ const userActivation =
160
+ typeof navigator !== 'undefined'
161
+ ? (navigator as {userActivation?: {hasBeenActive: boolean; isActive: boolean}})
162
+ .userActivation
163
+ : undefined;
164
+
165
+ if (userActivation) {
166
+ return {
167
+ hasBeenActive: userActivation.hasBeenActive,
168
+ isActive: userActivation.isActive,
169
+ };
170
+ }
171
+
172
+ return undefined;
173
+ }
174
+
175
+ /**
176
+ * Returns the telemetryOptOut value of the current user
177
+ * @returns one of 'manual', 'automatic', undefined
178
+ */
179
+ public getTelemetryOptOut() {
180
+ if (this.isTelemetryOptOutManual) {
181
+ return 'manual';
182
+ }
183
+ if (this.isTelemetryOptOutAutomatic) {
184
+ return 'automatic';
185
+ }
186
+
187
+ return undefined;
188
+ }
189
+
190
+ /**
191
+ * Sets the manual telemetry opt-out status for the current user
192
+ * @param value - boolean value indicating manual telemetry opt-out status
193
+ */
194
+ public setIsTelemetryOptOutManual(value: boolean) {
195
+ this.isTelemetryOptOutManual = value;
196
+ }
197
+
198
+ /**
199
+ * Sets the automatic telemetry opt-out status for the current user
200
+ * @param value - boolean value indicating automatic telemetry opt-out status
201
+ */
202
+ public setIsTelemetryOptOutAutomatic(value: boolean) {
203
+ this.isTelemetryOptOutAutomatic = value;
204
+ }
205
+
148
206
  /**
149
207
  * Returns if the meeting has converged architecture enabled
150
208
  * @param options.meetingId
@@ -190,6 +248,11 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
190
248
 
191
249
  // if ConvergedArchitecture enable and isConvergedWebinarWebcast -- then webcast
192
250
  if (meetingInfo?.enableConvergedArchitecture && meetingInfo?.enableEvent) {
251
+ // if enableConvergedWebinarLargeScale - then large scale webinar
252
+ if (meetingInfo?.enableConvergedWebinarLargeScale) {
253
+ return WEBEX_SUB_SERVICE_TYPES.LARGE_SCALE_WEBINAR;
254
+ }
255
+
193
256
  return meetingInfo?.isConvergedWebinarWebcast
194
257
  ? WEBEX_SUB_SERVICE_TYPES.WEBCAST
195
258
  : WEBEX_SUB_SERVICE_TYPES.WEBINAR;
@@ -213,12 +276,12 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
213
276
  getOrigin(options: GetOriginOptions, meetingId?: string) {
214
277
  const defaultClientType: ClientType =
215
278
  // @ts-ignore
216
- this.webex.meetings.config?.metrics?.clientType;
279
+ this.webex.meetings?.config?.metrics?.clientType;
217
280
  const defaultSubClientType: SubClientType =
218
281
  // @ts-ignore
219
- this.webex.meetings.config?.metrics?.subClientType;
282
+ this.webex.meetings?.config?.metrics?.subClientType;
220
283
  // @ts-ignore
221
- const providedClientVersion: string = this.webex.meetings.config?.metrics?.clientVersion;
284
+ const providedClientVersion: string = this.webex.meetings?.config?.metrics?.clientVersion;
222
285
  // @ts-ignore
223
286
  const defaultSDKClientVersion = `${CLIENT_NAME}/${this.webex.version}`;
224
287
 
@@ -229,6 +292,18 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
229
292
  versionMetadata = extractVersionMetadata(providedClientVersion);
230
293
  }
231
294
 
295
+ // Browser details and the support verdict can all be supplied by the host client, which is able
296
+ // to resolve wrapper browsers (Electron, WebOS) to the engine that actually determines
297
+ // capability. The browser fields fall back to the SDK's own detection when not supplied, the
298
+ // same way clientVersion falls back to the SDK version.
299
+ const {
300
+ browser: providedBrowser,
301
+ browserVersion: providedBrowserVersion,
302
+ isSupportedBrowserFamily,
303
+ isOutdatedBrowserVersion,
304
+ // @ts-ignore
305
+ } = this.webex.meetings?.config?.metrics ?? {};
306
+
232
307
  if (!this.hasLoggedBrowserSerial) {
233
308
  this.logger.log(
234
309
  CALL_DIAGNOSTIC_LOG_IDENTIFIER,
@@ -258,25 +333,27 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
258
333
  ...versionMetadata,
259
334
  publicNetworkPrefix:
260
335
  // @ts-ignore
261
- anonymizeIPAddress(this.webex.meetings.geoHintInfo?.clientAddress) || undefined,
336
+ anonymizeIPAddress(this.webex.meetings?.geoHintInfo?.clientAddress) || undefined,
262
337
  localNetworkPrefix:
263
338
  anonymizeIPAddress(
264
339
  // @ts-ignore
265
- this.webex.meetings.meetingCollection
266
- .get(meetingId)
340
+ this.webex.meetings?.meetingCollection
341
+ ?.get(meetingId)
267
342
  ?.statsAnalyzer?.getLocalIpAddress()
268
343
  ) || undefined,
269
344
  osVersion: getOSVersion() || 'unknown',
270
345
  subClientType: options?.subClientType || defaultSubClientType,
271
346
  os: getOSNameInternal(),
272
- browser: getBrowserName(),
273
- browserVersion: getBrowserVersion(),
347
+ browser: providedBrowser || getBrowserName(),
348
+ browserVersion: providedBrowserVersion || getBrowserVersion(),
349
+ ...(isSupportedBrowserFamily === undefined ? {} : {isSupportedBrowserFamily}),
350
+ ...(isOutdatedBrowserVersion === undefined ? {} : {isOutdatedBrowserVersion}),
274
351
  },
275
352
  };
276
353
 
277
354
  if (meetingId) {
278
355
  // @ts-ignore
279
- const meeting = this.webex.meetings.getBasicMeetingInformation(meetingId);
356
+ const meeting = this.webex.meetings?.getBasicMeetingInformation(meetingId);
280
357
  if (meeting?.environment) {
281
358
  origin.environment = meeting.environment;
282
359
  }
@@ -426,7 +503,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
426
503
  sent: 'not_defined_yet',
427
504
  },
428
505
  // @ts-ignore
429
- senderCountryCode: this.webex.meetings.geoHintInfo?.countryCode,
506
+ senderCountryCode: this.webex.meetings?.geoHintInfo?.countryCode,
430
507
  event: eventData,
431
508
  };
432
509
 
@@ -602,6 +679,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
602
679
  mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
603
680
  startTime: new Date().toISOString(),
604
681
  },
682
+ webexSubServiceType: this.getSubServiceType(meeting),
605
683
  };
606
684
 
607
685
  // merge any new properties, or override existing ones
@@ -984,7 +1062,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
984
1062
  sessionCorrelationId,
985
1063
  });
986
1064
 
987
- // create common event object structur
1065
+ // create common event object structure
988
1066
  const commonEventObject = {
989
1067
  name,
990
1068
  canProceed: true,
@@ -997,6 +1075,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
997
1075
  'loginType' in meeting.callStateForMetrics
998
1076
  ? meeting.callStateForMetrics.loginType
999
1077
  : this.getCurLoginType(),
1078
+ telemetryOptOut: this.getTelemetryOptOut(),
1000
1079
  isConvergedArchitectureEnabled: this.getIsConvergedArchitectureEnabled({
1001
1080
  meetingId,
1002
1081
  }),
@@ -1005,6 +1084,9 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1005
1084
  webexSubServiceType: this.getSubServiceType(meeting),
1006
1085
  // @ts-ignore
1007
1086
  webClientPreload: this.webex.meetings?.config?.metrics?.webClientPreload,
1087
+ isVipMeeting: meeting?.meetingInfo?.vipmeeting || false,
1088
+ isAutomatedUser: AutomatedUserUtils.isAutomatedUser(),
1089
+ userActivation: this.getUserActivation(),
1008
1090
  };
1009
1091
 
1010
1092
  const joinFlowVersion = options.joinFlowVersion ?? meeting.callStateForMetrics?.joinFlowVersion;
@@ -1124,8 +1206,11 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1124
1206
  isMercuryConnected: this.isMercuryConnected,
1125
1207
  },
1126
1208
  loginType: this.getCurLoginType(),
1209
+ telemetryOptOut: this.getTelemetryOptOut(),
1127
1210
  // @ts-ignore
1128
1211
  webClientPreload: this.webex.meetings?.config?.metrics?.webClientPreload,
1212
+ isAutomatedUser: AutomatedUserUtils.isAutomatedUser(),
1213
+ userActivation: this.getUserActivation(),
1129
1214
  };
1130
1215
 
1131
1216
  if (options.joinFlowVersion) {
@@ -1335,6 +1420,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1335
1420
  eventPayload: event,
1336
1421
  type: ['diagnostic-event'],
1337
1422
  };
1423
+
1338
1424
  this.preLoginMetricsBatcher.savePreLoginId(preLoginId);
1339
1425
 
1340
1426
  return this.preLoginMetricsBatcher.request(finalEvent);
@@ -1382,7 +1468,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
1382
1468
  },
1383
1469
  headers: {},
1384
1470
  // @ts-ignore
1385
- waitForServiceTimeout: this.webex.internal.metrics.config.waitForServiceTimeout,
1471
+ waitForServiceTimeout: this.webex.internal.metrics?.config?.waitForServiceTimeout,
1386
1472
  };
1387
1473
 
1388
1474
  if (options.preLoginId) {
@@ -390,6 +390,11 @@ export const prepareDiagnosticMetricItem = (webex: any, item: any) => {
390
390
 
391
391
  item.eventPayload.origin = Object.assign(origin, item.eventPayload.origin);
392
392
 
393
+ // Mark call milestones in logs for easier filtering and analysis
394
+ if (eventName) {
395
+ webex.logger.log('Milestone,CallDiagnostic', eventName);
396
+ }
397
+
393
398
  webex.logger.log(
394
399
  `CallDiagnosticLatencies,prepareDiagnosticMetricItem: ${JSON.stringify({
395
400
  latencies: Object.fromEntries(cdl.latencyTimestamps),
@@ -26,6 +26,7 @@ export const WEBEX_SUB_SERVICE_TYPES: Record<string, ClientSubServiceType> = {
26
26
  SCHEDULED_MEETING: 'ScheduledMeeting',
27
27
  WEBINAR: 'Webinar',
28
28
  WEBCAST: 'Webcast',
29
+ LARGE_SCALE_WEBINAR: 'LargeScaleWebinar',
29
30
  };
30
31
 
31
32
  // Found in https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
@@ -136,6 +137,7 @@ export const ERROR_DESCRIPTIONS = {
136
137
  WEBRTC_API_NOT_AVAILABLE: 'WebrtcApiNotAvailableError',
137
138
  WDM_RESTRICTED_REGION: 'WdmRestrictedRegion',
138
139
  USER_NOT_ALLOWED_JOIN_WEBINAR: 'UserNotAllowedJoinWebinar',
140
+ INVALID_MEETING_INFO: 'InvalidMeetingInfo',
139
141
  };
140
142
 
141
143
  export const SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP = {
@@ -146,6 +148,8 @@ export const SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP = {
146
148
  99002: 4100,
147
149
  // Cannot find the data. Unkown meeting.
148
150
  99009: 4100,
151
+ // The input parameters contain invalid item
152
+ 99019: 4105,
149
153
  // Meeting is not allow to cross env
150
154
  58500: 4100,
151
155
  // Input parameters contain invalit item
@@ -201,6 +205,8 @@ export const SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP = {
201
205
  423005: 4005,
202
206
  // Wrong password or host key with too many requests
203
207
  423006: 4005,
208
+ // PanelistPasswordError too many time,please input captcha code
209
+ 423008: 4005,
204
210
  // PasswordError with right captcha, please input captcha code
205
211
  423010: 4005,
206
212
  // PasswordOrHostKeyError with right captcha, please input captcha code
@@ -227,6 +233,8 @@ export const SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP = {
227
233
  403003: 4101,
228
234
  // Attendee email is required
229
235
  403030: 4101,
236
+ // webinar need login when un-invited attendee join
237
+ 403106: 4104,
230
238
 
231
239
  // ---- Locus ------
232
240
  // FREE_USER_MAX_PARTICIPANTS_EXCEEDED
@@ -696,6 +704,12 @@ export const CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD: Record<number, Partial<ClientEv
696
704
  category: 'expected',
697
705
  fatal: true,
698
706
  },
707
+ 4105: {
708
+ errorDescription: ERROR_DESCRIPTIONS.INVALID_MEETING_INFO,
709
+ category: 'expected',
710
+ fatal: false,
711
+ shownToUser: true,
712
+ },
699
713
  2729: {
700
714
  errorDescription: ERROR_DESCRIPTIONS.NO_MEDIA_FOUND,
701
715
  category: 'expected',
package/src/config.js CHANGED
@@ -19,6 +19,7 @@ export default {
19
19
  batcherMaxCalls: 50,
20
20
  batcherMaxWait: 1500,
21
21
  batcherRetryPlateau: 32000,
22
+ batcherRetryOnNetworkError: true,
22
23
  waitForServiceTimeout: 30,
23
24
  },
24
25
  };
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import Metrics from './metrics';
8
8
  import config from './config';
9
9
  import NewMetrics from './new-metrics';
10
10
  import * as Utils from './utils';
11
+ import * as AutomatedUserUtils from './automated-user';
11
12
  import {
12
13
  ClientEvent,
13
14
  ClientEventLeaveReason,
@@ -19,6 +20,8 @@ import {
19
20
  SubmitMQE,
20
21
  PreComputedLatencies,
21
22
  SubmitFeatureEvent,
23
+ LocusSyncLatencyEventName,
24
+ PrivacyAndSecurityPermission,
22
25
  } from './metrics.types';
23
26
  import * as CALL_DIAGNOSTIC_CONFIG from './call-diagnostic/config';
24
27
  import * as CallDiagnosticUtils from './call-diagnostic/call-diagnostic-metrics.util';
@@ -43,6 +46,7 @@ export {default, getOSNameInternal} from './metrics';
43
46
  export {
44
47
  config,
45
48
  CALL_DIAGNOSTIC_CONFIG,
49
+ AutomatedUserUtils,
46
50
  NewMetrics,
47
51
  Utils,
48
52
  CallDiagnosticUtils,
@@ -54,6 +58,7 @@ export {
54
58
  RtcMetrics,
55
59
  PreLoginMetrics,
56
60
  };
61
+ export {isAutomatedUser, isAutomatedUserAgent} from './automated-user';
57
62
  export type {
58
63
  ClientEvent,
59
64
  ClientEventLeaveReason,
@@ -65,4 +70,6 @@ export type {
65
70
  SubmitBusinessEvent,
66
71
  PreComputedLatencies,
67
72
  SubmitFeatureEvent,
73
+ LocusSyncLatencyEventName,
74
+ PrivacyAndSecurityPermission,
68
75
  };
@@ -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'];
@@ -149,6 +158,17 @@ export type SubmitMQEOptions = {
149
158
  globalMeetingId?: string;
150
159
  };
151
160
 
161
+ export const LOCUS_SYNC_LATENCY_EVENT_NAMES = [
162
+ 'internal.client.locus.sync.start',
163
+ 'internal.client.locus.hashtree.request',
164
+ 'internal.client.locus.hashtree.response',
165
+ 'internal.client.locus.sync.request',
166
+ 'internal.client.locus.sync.response',
167
+ 'internal.client.locus.sync.message.received',
168
+ ] as const;
169
+
170
+ export type LocusSyncLatencyEventName = (typeof LOCUS_SYNC_LATENCY_EVENT_NAMES)[number];
171
+
152
172
  export type InternalEvent = {
153
173
  name:
154
174
  | 'internal.client.meetinginfo.request'
@@ -156,14 +176,14 @@ export type InternalEvent = {
156
176
  | 'internal.register.device.request'
157
177
  | 'internal.register.device.response'
158
178
  | 'internal.reset.join.latencies'
159
- | 'internal.client.meeting.click.joinbutton'
160
179
  | 'internal.host.meeting.participant.admitted'
161
180
  | 'internal.client.meeting.interstitial-window.showed'
162
181
  | 'internal.client.interstitial-window.click.joinbutton'
163
182
  | 'internal.client.add-media.turn-discovery.start'
164
183
  | 'internal.client.add-media.turn-discovery.end'
165
184
  | 'internal.client.share.initiated'
166
- | 'internal.client.share.stopped';
185
+ | 'internal.client.share.stopped'
186
+ | LocusSyncLatencyEventName;
167
187
 
168
188
  payload?: never;
169
189
  options?: never;
@@ -319,12 +339,14 @@ export type PreComputedLatencies =
319
339
  | 'internal.get.cluster.time'
320
340
  | 'internal.click.to.interstitial'
321
341
  | 'internal.click.to.interstitial.with.user.delay'
342
+ | 'internal.click.to.interstitial.for.client.jmt'
322
343
  | 'internal.refresh.captcha.time'
323
344
  | 'internal.exchange.ci.token.time'
324
345
  | 'internal.get.u2c.time'
325
346
  | 'internal.call.init.join.req'
326
347
  | 'internal.other.app.api.time'
327
- | 'internal.api.fetch.intelligence.models';
348
+ | 'internal.api.fetch.intelligence.models'
349
+ | 'internal.client.locus.sync.random.backoff';
328
350
 
329
351
  export interface IdType {
330
352
  meetingId?: string;
@@ -19,17 +19,19 @@ import {
19
19
  ClientEvent,
20
20
  FeatureEvent,
21
21
  EventPayload,
22
- OperationalEvent,
23
22
  MediaQualityEvent,
24
23
  InternalEvent,
25
24
  SubmitClientEventOptions,
26
25
  Table,
27
26
  DelayedClientEvent,
28
27
  DelayedClientFeatureEvent,
28
+ PrivacyAndSecurityPermission,
29
29
  } from './metrics.types';
30
30
  import CallDiagnosticLatencies from './call-diagnostic/call-diagnostic-metrics-latencies';
31
31
  import {setMetricTimings} from './call-diagnostic/call-diagnostic-metrics.util';
32
32
  import {generateCommonErrorMetadata} from './utils';
33
+ import {isAutomatedUser as detectAutomatedUser} from './automated-user';
34
+ import PrivacyAndSecurityPermissionEnricher from './privacy-and-security-permission-enricher';
33
35
 
34
36
  /**
35
37
  * Metrics plugin to centralize all types of metrics.
@@ -67,6 +69,8 @@ class Metrics extends WebexPlugin {
67
69
 
68
70
  delayedClientFeatureEventsOverrides: Partial<DelayedClientFeatureEvent['options']> = {};
69
71
 
72
+ private privacyAndSecurityPermissionEnricher: PrivacyAndSecurityPermissionEnricher;
73
+
70
74
  /**
71
75
  * Constructor
72
76
  * @param args
@@ -77,8 +81,19 @@ class Metrics extends WebexPlugin {
77
81
  constructor(...args) {
78
82
  super(...args);
79
83
 
84
+ this.privacyAndSecurityPermissionEnricher = new PrivacyAndSecurityPermissionEnricher(
85
+ (error) => {
86
+ // @ts-ignore
87
+ this.webex.logger.error(
88
+ 'NewMetrics: @submitClientEvent. Privacy and security permission enrichment failed.',
89
+ error
90
+ );
91
+ }
92
+ );
80
93
  // @ts-ignore
81
94
  this.callDiagnosticLatencies = new CallDiagnosticLatencies({}, {parent: this.webex});
95
+ // @ts-ignore
96
+ this.callDiagnosticMetrics = new CallDiagnosticMetrics({}, {parent: this.webex});
82
97
  this.onReady();
83
98
  }
84
99
 
@@ -88,8 +103,6 @@ class Metrics extends WebexPlugin {
88
103
  private onReady() {
89
104
  // @ts-ignore
90
105
  this.webex.once('ready', () => {
91
- // @ts-ignore
92
- this.callDiagnosticMetrics = new CallDiagnosticMetrics({}, {parent: this.webex});
93
106
  this.preLoginMetrics = new PreLoginMetrics(
94
107
  // @ts-ignore
95
108
  new PreLoginMetricsBatcher({}, {parent: this.webex}),
@@ -182,6 +195,13 @@ class Metrics extends WebexPlugin {
182
195
  return this.businessMetrics?.isReadyToSubmitEvents() ?? false;
183
196
  }
184
197
 
198
+ /**
199
+ * @returns whether the current user agent belongs to an automated user
200
+ */
201
+ isAutomatedUser() {
202
+ return detectAutomatedUser();
203
+ }
204
+
185
205
  /**
186
206
  * Behavioral event
187
207
  * @param args
@@ -325,7 +345,7 @@ class Metrics extends WebexPlugin {
325
345
  payload?: RecursivePartial<FeatureEvent['payload']>;
326
346
  options: any;
327
347
  }) {
328
- if (!this.callDiagnosticLatencies || !this.callDiagnosticMetrics) {
348
+ if (!this.isReady) {
329
349
  // @ts-ignore
330
350
  this.webex.logger.log(
331
351
  `NewMetrics: @submitFeatureEvent. Attempted to submit before webex.ready. Event name: ${name}`
@@ -360,7 +380,7 @@ class Metrics extends WebexPlugin {
360
380
  payload?: RecursivePartial<ClientEvent['payload']>;
361
381
  options?: SubmitClientEventOptions;
362
382
  }): Promise<any> {
363
- if (!this.callDiagnosticLatencies || !this.callDiagnosticMetrics) {
383
+ if (!this.isReady) {
364
384
  // @ts-ignore
365
385
  this.webex.logger.log(
366
386
  `NewMetrics: @submitClientEvent. Attempted to submit before webex.ready. Event name: ${name}`
@@ -373,14 +393,41 @@ class Metrics extends WebexPlugin {
373
393
  options: {meetingId: options?.meetingId},
374
394
  });
375
395
 
376
- return this.callDiagnosticMetrics.submitClientEvent({
396
+ const enrichedPayload = this.privacyAndSecurityPermissionEnricher.enrich({
377
397
  name,
378
398
  payload,
399
+ scope: this.getPermissionScope(options),
400
+ });
401
+
402
+ return this.callDiagnosticMetrics.submitClientEvent({
403
+ name,
404
+ payload: enrichedPayload,
379
405
  options,
380
406
  delaySubmitEvent: this.delaySubmitClientEvents,
381
407
  });
382
408
  }
383
409
 
410
+ /**
411
+ * Stores the latest browser permission state supplied by the client.
412
+ * @param permission latest normalized permission snapshot
413
+ */
414
+ public setPrivacyAndSecurityPermission(permission: PrivacyAndSecurityPermission): void {
415
+ this.privacyAndSecurityPermissionEnricher.setPermission(permission);
416
+ }
417
+
418
+ /**
419
+ * Resolves event options to the call identity used by Call Diagnostic where possible.
420
+ * @param options client event options
421
+ * @returns the permission history scope
422
+ */
423
+ private getPermissionScope(options?: SubmitClientEventOptions): string {
424
+ const meeting = options?.meetingId
425
+ ? (this as any).webex.meetings?.getBasicMeetingInformation?.(options.meetingId)
426
+ : undefined;
427
+
428
+ return options?.correlationId ?? meeting?.correlationId ?? 'default';
429
+ }
430
+
384
431
  /**
385
432
  * Issue request to alias a user's pre-login ID with their CI UUID
386
433
  * @param {string} preLoginId
@@ -0,0 +1,33 @@
1
+ import type {ClientEvent} from '../metrics.types';
2
+ import type {PermissionEnrichmentPolicy} from './types';
3
+
4
+ export const CAMERA_AND_MICROPHONE_PERMISSION_EVENTS = new Set<ClientEvent['name']>([
5
+ 'client.call.initiated',
6
+ 'client.media.capabilities',
7
+ 'client.ice.end',
8
+ 'client.locus.join.request',
9
+ 'client.locus.join.response',
10
+ 'client.media-engine.ready',
11
+ ]);
12
+
13
+ export const MEDIA_TX_PERMISSION_EVENTS = new Set<ClientEvent['name']>([
14
+ 'client.media.tx.start',
15
+ 'client.media.tx.stop',
16
+ ]);
17
+
18
+ export const CONTENT_SHARE_PERMISSION_EVENTS = new Set<ClientEvent['name']>([
19
+ 'client.share.initiated',
20
+ 'client.share.floor-grant.request',
21
+ 'client.share.floor-granted.local',
22
+ ]);
23
+
24
+ export const FINAL_PERMISSION_EVENTS = new Set<ClientEvent['name']>([
25
+ 'client.call.leave',
26
+ 'client.call.remote-ended',
27
+ 'client.call.aborted',
28
+ ]);
29
+
30
+ export const NO_PERMISSION_ENRICHMENT: PermissionEnrichmentPolicy = {
31
+ resources: [],
32
+ terminal: false,
33
+ };