@webex/internal-plugin-metrics 3.0.0-beta.19 → 3.0.0-beta.190

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 (67) hide show
  1. package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js +134 -0
  2. package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js.map +1 -0
  3. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +451 -0
  4. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -0
  5. package/dist/call-diagnostic/call-diagnostic-metrics.js +511 -0
  6. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -0
  7. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +106 -0
  8. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -0
  9. package/dist/call-diagnostic/config.js +461 -0
  10. package/dist/call-diagnostic/config.js.map +1 -0
  11. package/dist/call-diagnostic/generated-types-temp/ClientEvent.js +7 -0
  12. package/dist/call-diagnostic/generated-types-temp/ClientEvent.js.map +1 -0
  13. package/dist/call-diagnostic/generated-types-temp/Event.js +7 -0
  14. package/dist/call-diagnostic/generated-types-temp/Event.js.map +1 -0
  15. package/dist/call-diagnostic/generated-types-temp/MediaQualityEvent.js +7 -0
  16. package/dist/call-diagnostic/generated-types-temp/MediaQualityEvent.js.map +1 -0
  17. package/dist/config.js +20 -1
  18. package/dist/config.js.map +1 -1
  19. package/dist/index.js +25 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/metrics.js +30 -30
  22. package/dist/metrics.js.map +1 -1
  23. package/dist/metrics.types.js +7 -0
  24. package/dist/metrics.types.js.map +1 -0
  25. package/dist/new-metrics.js +185 -0
  26. package/dist/new-metrics.js.map +1 -0
  27. package/dist/types/batcher.d.ts +2 -0
  28. package/dist/types/call-diagnostic/call-diagnostic-metrics-batcher.d.ts +2 -0
  29. package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +189 -0
  30. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +324 -0
  31. package/dist/types/call-diagnostic/call-diagnostic-metrics.util.d.ts +31 -0
  32. package/dist/types/call-diagnostic/config.d.ts +57 -0
  33. package/dist/types/call-diagnostic/generated-types-temp/ClientEvent.d.ts +1112 -0
  34. package/dist/types/call-diagnostic/generated-types-temp/Event.d.ts +4851 -0
  35. package/dist/types/call-diagnostic/generated-types-temp/MediaQualityEvent.d.ts +2121 -0
  36. package/dist/types/client-metrics-batcher.d.ts +2 -0
  37. package/dist/types/config.d.ts +35 -0
  38. package/dist/types/index.d.ts +11 -0
  39. package/dist/types/metrics.d.ts +3 -0
  40. package/dist/types/metrics.types.d.ts +87 -0
  41. package/dist/types/new-metrics.d.ts +83 -0
  42. package/package.json +12 -8
  43. package/src/call-diagnostic/call-diagnostic-metrics-batcher.ts +148 -0
  44. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +408 -0
  45. package/src/call-diagnostic/call-diagnostic-metrics.ts +528 -0
  46. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +102 -0
  47. package/src/call-diagnostic/config.ts +455 -0
  48. package/src/call-diagnostic/generated-types-temp/ClientEvent.ts +2357 -0
  49. package/src/call-diagnostic/generated-types-temp/Event.ts +7669 -0
  50. package/src/call-diagnostic/generated-types-temp/MediaQualityEvent.ts +2321 -0
  51. package/src/config.js +19 -0
  52. package/src/index.ts +39 -0
  53. package/src/metrics.js +25 -27
  54. package/src/metrics.types.ts +131 -0
  55. package/src/new-metrics.ts +170 -0
  56. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +243 -0
  57. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +474 -0
  58. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +722 -0
  59. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +76 -0
  60. package/test/unit/spec/metrics.js +65 -97
  61. package/test/unit/spec/new-metrics.ts +91 -0
  62. package/tsconfig.json +6 -0
  63. package/dist/call-diagnostic-events-batcher.js +0 -60
  64. package/dist/call-diagnostic-events-batcher.js.map +0 -1
  65. package/src/call-diagnostic-events-batcher.js +0 -62
  66. package/src/index.js +0 -17
  67. package/test/unit/spec/call-diagnostic-events-batcher.js +0 -195
@@ -0,0 +1,528 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /* eslint-disable class-methods-use-this */
3
+ /* eslint-disable valid-jsdoc */
4
+ import {getOSNameInternal} from '@webex/internal-plugin-metrics';
5
+ import {BrowserDetection} from '@webex/common';
6
+ import uuid from 'uuid';
7
+ import {merge} from 'lodash';
8
+ import {StatelessWebexPlugin} from '@webex/webex-core';
9
+
10
+ import {
11
+ anonymizeIPAddress,
12
+ clearEmptyKeysRecursively,
13
+ isLocusServiceErrorCode,
14
+ userAgentToString,
15
+ } from './call-diagnostic-metrics.util';
16
+ import {CLIENT_NAME} from '../config';
17
+ import {
18
+ RecursivePartial,
19
+ Event,
20
+ ClientType,
21
+ SubClientType,
22
+ NetworkType,
23
+ ClientEvent,
24
+ SubmitClientEventOptions,
25
+ MediaQualityEvent,
26
+ SubmitMQEOptions,
27
+ SubmitMQEPayload,
28
+ ClientEventError,
29
+ ClientEventPayload,
30
+ } from '../metrics.types';
31
+ import CallDiagnosticEventsBatcher from './call-diagnostic-metrics-batcher';
32
+ import {
33
+ CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD,
34
+ CALL_DIAGNOSTIC_EVENT_FAILED_TO_SEND,
35
+ MEETING_INFO_LOOKUP_ERROR_CLIENT_CODE,
36
+ NEW_LOCUS_ERROR_CLIENT_CODE,
37
+ SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP,
38
+ } from './config';
39
+
40
+ const {getOSVersion, getBrowserName, getBrowserVersion} = BrowserDetection();
41
+
42
+ type GetOriginOptions = {
43
+ clientType: ClientType;
44
+ subClientType: SubClientType;
45
+ networkType?: NetworkType;
46
+ };
47
+
48
+ type GetIdentifiersOptions = {
49
+ meeting?: any;
50
+ mediaConnections?: any[];
51
+ correlationId?: string;
52
+ };
53
+
54
+ /**
55
+ * @description Util class to handle Call Analyzer Metrics
56
+ * @export
57
+ * @class CallDiagnosticMetrics
58
+ */
59
+ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
60
+ // @ts-ignore
61
+ private callDiagnosticEventsBatcher: CallDiagnosticEventsBatcher;
62
+
63
+ /**
64
+ * Constructor
65
+ * @param args
66
+ */
67
+ constructor(...args) {
68
+ super(...args);
69
+ // @ts-ignore
70
+ this.callDiagnosticEventsBatcher = new CallDiagnosticEventsBatcher({}, {parent: this.webex});
71
+ }
72
+
73
+ /**
74
+ * Returns the login type of the current user
75
+ * @returns one of 'login-ci','unverified-guest', null
76
+ */
77
+ getCurLoginType() {
78
+ // @ts-ignore
79
+ if (this.webex.canAuthorize) {
80
+ // @ts-ignore
81
+ return this.webex.credentials.isUnverifiedGuest ? 'unverified-guest' : 'login-ci';
82
+ }
83
+
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Get origin object for Call Diagnostic Event payload.
89
+ * @param options
90
+ * @param meetingId
91
+ * @returns
92
+ */
93
+ getOrigin(options: GetOriginOptions, meetingId?: string) {
94
+ const defaultClientType: ClientType =
95
+ // @ts-ignore
96
+ this.webex.meetings.config?.metrics?.clientType;
97
+ const defaultSubClientType: SubClientType =
98
+ // @ts-ignore
99
+ this.webex.meetings.config?.metrics?.subClientType;
100
+
101
+ if (
102
+ (defaultClientType && defaultSubClientType) ||
103
+ (options.clientType && options.subClientType)
104
+ ) {
105
+ const origin: Event['origin'] = {
106
+ name: 'endpoint',
107
+ networkType: options?.networkType || 'unknown',
108
+ userAgent: userAgentToString({
109
+ // @ts-ignore
110
+ clientName: this.webex.meetings?.metrics?.clientName,
111
+ // @ts-ignore
112
+ webexVersion: this.webex.version,
113
+ }),
114
+ clientInfo: {
115
+ clientType: options?.clientType || defaultClientType,
116
+ // @ts-ignore
117
+ clientVersion: `${CLIENT_NAME}/${this.webex.version}`,
118
+ localNetworkPrefix:
119
+ // @ts-ignore
120
+ anonymizeIPAddress(this.webex.meetings.geoHintInfo?.clientAddress) || undefined,
121
+ osVersion: getOSVersion() || 'unknown',
122
+ subClientType: options?.subClientType || defaultSubClientType,
123
+ os: getOSNameInternal(),
124
+ browser: getBrowserName(),
125
+ browserVersion: getBrowserVersion(),
126
+ },
127
+ };
128
+
129
+ if (meetingId) {
130
+ // @ts-ignore
131
+ const meeting = this.webex.meetings.meetingCollection.get(meetingId);
132
+ if (meeting?.environment) {
133
+ origin.environment = meeting.environment;
134
+ }
135
+ }
136
+
137
+ return origin;
138
+ }
139
+
140
+ throw new Error("ClientType and SubClientType can't be undefined");
141
+ }
142
+
143
+ /**
144
+ * Gather identifier details for call diagnostic payload.
145
+ * @throws Error if initialization fails.
146
+ * @param options
147
+ */
148
+ getIdentifiers(options: GetIdentifiersOptions) {
149
+ const {meeting, mediaConnections, correlationId} = options;
150
+ const identifiers: Event['event']['identifiers'] = {correlationId: 'unknown'};
151
+
152
+ if (meeting) {
153
+ identifiers.correlationId = meeting.correlationId;
154
+ }
155
+
156
+ if (correlationId) {
157
+ identifiers.correlationId = correlationId;
158
+ }
159
+ // @ts-ignore
160
+ if (this.webex.internal) {
161
+ // @ts-ignore
162
+ const {device} = this.webex.internal;
163
+ identifiers.userId = device.userId;
164
+ identifiers.deviceId = device.url;
165
+ identifiers.orgId = device.orgId;
166
+ // @ts-ignore
167
+ identifiers.locusUrl = this.webex.internal.services.get('locus');
168
+ }
169
+
170
+ if (meeting?.locusInfo?.fullState) {
171
+ identifiers.locusUrl = meeting.locusUrl;
172
+ identifiers.locusId = meeting.locusUrl && meeting.locusUrl.split('/').pop();
173
+ identifiers.locusStartTime =
174
+ meeting.locusInfo.fullState && meeting.locusInfo.fullState.lastActive;
175
+ }
176
+
177
+ if (mediaConnections) {
178
+ identifiers.mediaAgentAlias = mediaConnections?.[0]?.mediaAgentAlias;
179
+ identifiers.mediaAgentGroupId = mediaConnections?.[0]?.mediaAgentGroupId;
180
+ }
181
+
182
+ if (identifiers.correlationId === undefined) {
183
+ throw new Error('Identifiers initialization failed.');
184
+ }
185
+
186
+ return identifiers;
187
+ }
188
+
189
+ /**
190
+ * Create diagnostic event, which can hold client event, feature event or MQE event data.
191
+ * This just initiates the shared properties that are required for all the 3 event categories.
192
+ * @param eventData
193
+ * @param options
194
+ * @returns
195
+ */
196
+ prepareDiagnosticEvent(eventData: Event['event'], options: any) {
197
+ const {meetingId} = options;
198
+ const origin = this.getOrigin(options, meetingId);
199
+
200
+ const event: Event = {
201
+ eventId: uuid.v4(),
202
+ version: 1,
203
+ origin,
204
+ originTime: {
205
+ triggered: new Date().toISOString(),
206
+ // is overridden in prepareRequest batcher
207
+ sent: 'not_defined_yet',
208
+ },
209
+ // @ts-ignore
210
+ senderCountryCode: this.webex.meetings.geoHintInfo?.countryCode,
211
+ event: eventData,
212
+ };
213
+
214
+ // sanitize (remove empty properties, CA requires it)
215
+ // but we don't want to sanitize MQE as most of the times
216
+ // values will be 0, [] etc, and they are required.
217
+ if (eventData.name !== 'client.mediaquality.event') {
218
+ clearEmptyKeysRecursively(event);
219
+ }
220
+
221
+ return event;
222
+ }
223
+
224
+ /**
225
+ * TODO: NOT IMPLEMENTED
226
+ * Submit Feature Event
227
+ * @returns
228
+ */
229
+ public submitFeatureEvent() {
230
+ throw Error('Not implemented');
231
+ }
232
+
233
+ /**
234
+ * Submit Media Quality Event
235
+ * @param args - submit params
236
+ * @param arg.name - event key
237
+ * @param arg.payload - additional payload to be merge with the default payload
238
+ * @param arg.options - options
239
+ */
240
+ submitMQE({
241
+ name,
242
+ payload,
243
+ options,
244
+ }: {
245
+ name: MediaQualityEvent['name'];
246
+ payload: SubmitMQEPayload;
247
+ options: SubmitMQEOptions;
248
+ }) {
249
+ const {meetingId, mediaConnections} = options;
250
+
251
+ // events that will most likely happen in join phase
252
+ if (meetingId) {
253
+ // @ts-ignore
254
+ const meeting = this.webex.meetings.meetingCollection.get(meetingId);
255
+
256
+ if (!meeting) {
257
+ console.warn(
258
+ 'Attempt to send MQE but no meeting was found...',
259
+ `event: ${name}, meetingId: ${meetingId}`
260
+ );
261
+ // @ts-ignore
262
+ this.webex.internal.metrics.submitClientMetrics(CALL_DIAGNOSTIC_EVENT_FAILED_TO_SEND, {
263
+ fields: {
264
+ meetingId,
265
+ name,
266
+ },
267
+ });
268
+
269
+ return;
270
+ }
271
+
272
+ // merge identifiers
273
+ const identifiers = this.getIdentifiers({
274
+ meeting,
275
+ mediaConnections: meeting.mediaConnections || mediaConnections,
276
+ });
277
+
278
+ // create media quality event object
279
+ let clientEventObject: MediaQualityEvent['payload'] = {
280
+ name,
281
+ canProceed: true,
282
+ identifiers,
283
+ eventData: {
284
+ webClientDomain: window.location.hostname,
285
+ },
286
+ intervals: payload.intervals,
287
+ sourceMetadata: {
288
+ applicationSoftwareType: CLIENT_NAME,
289
+ // @ts-ignore
290
+ applicationSoftwareVersion: this.webex.version,
291
+ mediaEngineSoftwareType: getBrowserName() || 'browser',
292
+ mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
293
+ startTime: new Date().toISOString(),
294
+ },
295
+ };
296
+
297
+ // merge any new properties, or override existing ones
298
+ clientEventObject = merge(clientEventObject, payload);
299
+
300
+ // append media quality event data to the call diagnostic event
301
+ const diagnosticEvent = this.prepareDiagnosticEvent(clientEventObject, options);
302
+ this.submitToCallDiagnostics(diagnosticEvent);
303
+ } else {
304
+ throw new Error(
305
+ 'Media quality events cant be sent outside the context of a meeting. Meeting id is required.'
306
+ );
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Return Client Event payload by client error code
312
+ * @param arg - get error arg
313
+ * @param arg.clientErrorCode
314
+ * @param arg.serviceErrorCode
315
+ * @returns
316
+ */
317
+ public getErrorPayloadForClientErrorCode({
318
+ clientErrorCode,
319
+ serviceErrorCode,
320
+ }: {
321
+ clientErrorCode: number;
322
+ serviceErrorCode: any;
323
+ }): ClientEventError {
324
+ let error: ClientEventError;
325
+
326
+ if (clientErrorCode) {
327
+ const partialParsedError = CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD[clientErrorCode];
328
+
329
+ if (partialParsedError) {
330
+ error = merge(
331
+ {fatal: true, shownToUser: false, name: 'other', category: 'other'}, // default values
332
+ {errorCode: clientErrorCode},
333
+ {serviceErrorCode},
334
+ partialParsedError
335
+ );
336
+
337
+ return error;
338
+ }
339
+ }
340
+
341
+ return undefined;
342
+ }
343
+
344
+ /**
345
+ * Generate error payload for Client Event
346
+ * @param rawError
347
+ */
348
+ generateClientEventErrorPayload(rawError: any) {
349
+ const serviceErrorCode = rawError?.body?.errorCode || rawError?.body?.code;
350
+ if (serviceErrorCode) {
351
+ const clientErrorCode = SERVICE_ERROR_CODES_TO_CLIENT_ERROR_CODES_MAP[serviceErrorCode];
352
+ if (clientErrorCode) {
353
+ return this.getErrorPayloadForClientErrorCode({clientErrorCode, serviceErrorCode});
354
+ }
355
+
356
+ // by default, if it is locus error, return nre locus err
357
+ if (isLocusServiceErrorCode(serviceErrorCode)) {
358
+ return this.getErrorPayloadForClientErrorCode({
359
+ clientErrorCode: NEW_LOCUS_ERROR_CLIENT_CODE,
360
+ serviceErrorCode,
361
+ });
362
+ }
363
+
364
+ // otherwise return meeting info
365
+ return this.getErrorPayloadForClientErrorCode({
366
+ clientErrorCode: MEETING_INFO_LOOKUP_ERROR_CLIENT_CODE,
367
+ serviceErrorCode,
368
+ });
369
+ }
370
+
371
+ return undefined;
372
+ }
373
+
374
+ /**
375
+ * Create client event object for in meeting events
376
+ * @param arg - create args
377
+ * @param arg.event - event key
378
+ * @param arg.options - options
379
+ * @returns object
380
+ */
381
+ private createClientEventObjectInMeeting({
382
+ name,
383
+ options,
384
+ }: {
385
+ name: ClientEvent['name'];
386
+ options: SubmitClientEventOptions;
387
+ }) {
388
+ const {meetingId, mediaConnections, rawError} = options;
389
+
390
+ // @ts-ignore
391
+ const meeting = this.webex.meetings.meetingCollection.get(meetingId);
392
+
393
+ if (!meeting) {
394
+ console.warn(
395
+ 'Attempt to send client event but no meeting was found...',
396
+ `event: ${name}, meetingId: ${meetingId}`
397
+ );
398
+ // @ts-ignore
399
+ this.webex.internal.metrics.submitClientMetrics(CALL_DIAGNOSTIC_EVENT_FAILED_TO_SEND, {
400
+ fields: {
401
+ meetingId,
402
+ name,
403
+ },
404
+ });
405
+
406
+ return undefined;
407
+ }
408
+
409
+ // grab identifiers
410
+ const identifiers = this.getIdentifiers({
411
+ meeting,
412
+ mediaConnections: meeting?.mediaConnections || mediaConnections,
413
+ });
414
+
415
+ // check if we need to generate errors
416
+ const errors: ClientEvent['payload']['errors'] = [];
417
+
418
+ if (rawError) {
419
+ const generatedError = this.generateClientEventErrorPayload(rawError);
420
+ if (generatedError) {
421
+ errors.push(generatedError);
422
+ }
423
+ }
424
+
425
+ // create client event object
426
+ const clientEventObject: ClientEvent['payload'] = {
427
+ name,
428
+ canProceed: true,
429
+ identifiers,
430
+ errors,
431
+ eventData: {
432
+ webClientDomain: window.location.hostname,
433
+ },
434
+ userType: meeting.getCurUserType(),
435
+ loginType: this.getCurLoginType(),
436
+ };
437
+
438
+ return clientEventObject;
439
+ }
440
+
441
+ /**
442
+ * Create client event object for pre meeting events
443
+ * @param arg - create args
444
+ * @param arg.event - event key
445
+ * @param arg.options - payload
446
+ * @returns object
447
+ */
448
+ private createClientEventObjectPreMeeting({
449
+ name,
450
+ options,
451
+ }: {
452
+ name: ClientEvent['name'];
453
+ options: SubmitClientEventOptions;
454
+ }) {
455
+ const {correlationId} = options;
456
+
457
+ // grab identifiers
458
+ const identifiers = this.getIdentifiers({
459
+ correlationId,
460
+ });
461
+
462
+ // create client event object
463
+ const clientEventObject: ClientEvent['payload'] = {
464
+ name,
465
+ canProceed: true,
466
+ identifiers,
467
+ eventData: {
468
+ webClientDomain: window.location.hostname,
469
+ },
470
+ loginType: this.getCurLoginType(),
471
+ };
472
+
473
+ return clientEventObject;
474
+ }
475
+
476
+ /**
477
+ * Submit Client Event CA event.
478
+ * @param arg - submit params
479
+ * @param arg.event - event key
480
+ * @param arg.payload - additional payload to be merged with default payload
481
+ * @param arg.options - payload
482
+ * @throws
483
+ */
484
+ public submitClientEvent({
485
+ name,
486
+ payload,
487
+ options,
488
+ }: {
489
+ name: ClientEvent['name'];
490
+ payload?: ClientEventPayload;
491
+ options: SubmitClientEventOptions;
492
+ }) {
493
+ const {meetingId, correlationId} = options;
494
+ let clientEventObject: ClientEvent['payload'];
495
+
496
+ // events that will most likely happen in join phase
497
+ if (meetingId) {
498
+ clientEventObject = this.createClientEventObjectInMeeting({name, options});
499
+ } else if (correlationId) {
500
+ // any pre join events or events that are outside the meeting.
501
+ clientEventObject = this.createClientEventObjectPreMeeting({name, options});
502
+ } else {
503
+ throw new Error('Not implemented');
504
+ }
505
+
506
+ // merge any new properties, or override existing ones
507
+ clientEventObject = merge(clientEventObject, payload);
508
+
509
+ // append client event data to the call diagnostic event
510
+ const diagnosticEvent = this.prepareDiagnosticEvent(clientEventObject, options);
511
+ this.submitToCallDiagnostics(diagnosticEvent);
512
+ }
513
+
514
+ /**
515
+ * Prepare the event and send the request to metrics-a service.
516
+ * @param event
517
+ * @returns promise
518
+ */
519
+ submitToCallDiagnostics(event: Event): Promise<void> {
520
+ // build metrics-a event type
521
+ const finalEvent = {
522
+ eventPayload: event,
523
+ type: ['diagnostic-event'],
524
+ };
525
+
526
+ return this.callDiagnosticEventsBatcher.request(finalEvent);
527
+ }
528
+ }
@@ -0,0 +1,102 @@
1
+ /* eslint-disable valid-jsdoc */
2
+ import anonymize from 'ip-anonymize';
3
+ import util from 'util';
4
+
5
+ import {BrowserDetection} from '@webex/common';
6
+ import {isEmpty} from 'lodash';
7
+
8
+ const {getOSName, getOSVersion, getBrowserName, getBrowserVersion} = BrowserDetection();
9
+
10
+ export const anonymizeIPAddress = (localIp) => anonymize(localIp, 28, 96);
11
+
12
+ /**
13
+ * Returns a formated string of the user agent.
14
+ *
15
+ * @returns {string} formatted user agent information
16
+ */
17
+ export const userAgentToString = ({clientName, webexVersion}) => {
18
+ let userAgentOption;
19
+ let browserInfo;
20
+ const clientInfo = util.format('client=%s', `${clientName}`);
21
+
22
+ if (
23
+ ['chrome', 'firefox', 'msie', 'msedge', 'safari'].indexOf(getBrowserName().toLowerCase()) !== -1
24
+ ) {
25
+ browserInfo = util.format(
26
+ 'browser=%s',
27
+ `${getBrowserName().toLowerCase()}/${getBrowserVersion().split('.')[0]}`
28
+ );
29
+ }
30
+ const osInfo = util.format('os=%s', `${getOSName()}/${getOSVersion().split('.')[0]}`);
31
+
32
+ if (browserInfo) {
33
+ userAgentOption = `(${browserInfo}`;
34
+ }
35
+ if (osInfo) {
36
+ userAgentOption = userAgentOption
37
+ ? `${userAgentOption}; ${clientInfo}; ${osInfo}`
38
+ : `${clientInfo}; (${osInfo}`;
39
+ }
40
+ if (userAgentOption) {
41
+ userAgentOption += ')';
42
+
43
+ return util.format(
44
+ 'webex-js-sdk/%s %s',
45
+ `${process.env.NODE_ENV}-${webexVersion}`,
46
+ userAgentOption
47
+ );
48
+ }
49
+
50
+ return util.format('webex-js-sdk/%s', `${process.env.NODE_ENV}-${webexVersion}`);
51
+ };
52
+
53
+ /**
54
+ * Iterates object recursively and removes any
55
+ * property that returns isEmpty for it's associated value
56
+ * isEmpty = implementation from Lodash.
57
+ *
58
+ * It modifies the object in place (mutable)
59
+ *
60
+ * @param obj - input
61
+ * @returns
62
+ */
63
+ export const clearEmptyKeysRecursively = (obj: any) => {
64
+ // Check if the object is empty
65
+ if (Object.keys(obj).length === 0) {
66
+ return;
67
+ }
68
+
69
+ Object.keys(obj).forEach((key) => {
70
+ if (
71
+ (typeof obj[key] === 'object' || typeof obj[key] === 'string' || Array.isArray(obj[key])) &&
72
+ isEmpty(obj[key])
73
+ ) {
74
+ delete obj[key];
75
+ }
76
+ if (Array.isArray(obj[key])) {
77
+ obj[key] = [...obj[key].filter((x) => !!x)];
78
+ }
79
+ if (typeof obj[key] === 'object') {
80
+ clearEmptyKeysRecursively(obj[key]);
81
+ }
82
+ });
83
+ };
84
+
85
+ /**
86
+ * Locus error codes start with 2. The next three digits are the
87
+ * HTTP status code related to the error code (like 400, 403, 502, etc.)
88
+ * The remaining three digits are just an increasing integer.
89
+ * If it is 7 digits and starts with a 2, it is locus.
90
+ *
91
+ * @param errorCode
92
+ * @returns
93
+ */
94
+ export const isLocusServiceErrorCode = (errorCode: string | number) => {
95
+ const code = `${errorCode}`;
96
+
97
+ if (code.length === 7 && code.charAt(0) === '2') {
98
+ return true;
99
+ }
100
+
101
+ return false;
102
+ };