@xh/hoist 72.3.0 → 72.4.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 (36) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +2 -2
  3. package/admin/tabs/cluster/instances/BaseInstanceModel.ts +1 -29
  4. package/admin/tabs/cluster/instances/services/DetailsPanel.ts +2 -1
  5. package/admin/tabs/cluster/objects/DetailModel.ts +4 -40
  6. package/admin/tabs/cluster/objects/DetailPanel.ts +2 -1
  7. package/appcontainer/AppContainerModel.ts +2 -0
  8. package/appcontainer/AppStateModel.ts +40 -8
  9. package/build/types/admin/tabs/cluster/instances/BaseInstanceModel.d.ts +1 -3
  10. package/build/types/admin/tabs/cluster/objects/DetailModel.d.ts +1 -3
  11. package/build/types/appcontainer/AppStateModel.d.ts +2 -0
  12. package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +7 -0
  13. package/build/types/core/XH.d.ts +11 -1
  14. package/build/types/format/FormatDate.d.ts +22 -1
  15. package/build/types/format/FormatMisc.d.ts +3 -2
  16. package/build/types/security/Types.d.ts +3 -3
  17. package/build/types/security/msal/MsalClient.d.ts +2 -0
  18. package/build/types/svc/ClientHealthService.d.ts +58 -0
  19. package/build/types/svc/TrackService.d.ts +0 -12
  20. package/build/types/svc/index.d.ts +1 -0
  21. package/build/types/utils/js/index.d.ts +0 -1
  22. package/cmp/viewmanager/ViewManagerModel.ts +10 -1
  23. package/core/XH.ts +26 -1
  24. package/desktop/cmp/viewmanager/ViewMenu.ts +11 -9
  25. package/format/FormatDate.ts +45 -3
  26. package/format/FormatMisc.ts +6 -4
  27. package/package.json +1 -1
  28. package/security/Types.ts +3 -3
  29. package/security/msal/MsalClient.ts +13 -13
  30. package/svc/ClientHealthService.ts +165 -0
  31. package/svc/TrackService.ts +3 -67
  32. package/svc/index.ts +1 -0
  33. package/tsconfig.tsbuildinfo +1 -1
  34. package/utils/js/index.ts +0 -1
  35. package/build/types/utils/js/BrowserUtils.d.ts +0 -41
  36. package/utils/js/BrowserUtils.ts +0 -103
package/core/XH.ts CHANGED
@@ -28,7 +28,8 @@ import {
28
28
  PrefService,
29
29
  SessionStorageService,
30
30
  TrackService,
31
- WebSocketService
31
+ WebSocketService,
32
+ ClientHealthService
32
33
  } from '@xh/hoist/svc';
33
34
  import {camelCase, flatten, isString, uniqueId} from 'lodash';
34
35
  import {Router, State} from 'router5';
@@ -64,6 +65,7 @@ import {
64
65
  import {installServicesAsync} from './impl/InstallServices';
65
66
  import {instanceManager} from './impl/InstanceManager';
66
67
  import {HoistModel, ModelSelector, RefreshContextModel} from './model';
68
+ import ShortUniqueId from 'short-unique-id';
67
69
 
68
70
  export const MIN_HOIST_CORE_VERSION = '28.0';
69
71
 
@@ -84,6 +86,15 @@ declare const xhIsDevelopmentMode: boolean;
84
86
  * Available via import as `XH` - also installed as `window.XH` for troubleshooting purposes.
85
87
  */
86
88
  export class XHApi {
89
+ /** Unique id for this loaded instance of the app. Unique for every refresh of document. */
90
+ clientId: string = this.genClientId();
91
+
92
+ /**
93
+ * Unique id for this browser tab/window on this domain.
94
+ * Corresponds to the scope of the built-in sessionStorage object.
95
+ */
96
+ sessionId: string = this.genSessionId();
97
+
87
98
  //--------------------------
88
99
  // Implementation Delegates
89
100
  //--------------------------
@@ -131,6 +142,7 @@ export class XHApi {
131
142
  alertBannerService: AlertBannerService;
132
143
  autoRefreshService: AutoRefreshService;
133
144
  changelogService: ChangelogService;
145
+ clientHealthService: ClientHealthService;
134
146
  configService: ConfigService;
135
147
  environmentService: EnvironmentService;
136
148
  fetchService: FetchService;
@@ -794,6 +806,19 @@ export class XHApi {
794
806
  private get acm(): AppContainerModel {
795
807
  return this.appContainerModel;
796
808
  }
809
+
810
+ private genClientId(): string {
811
+ return new ShortUniqueId({length: 8}).rnd();
812
+ }
813
+
814
+ private genSessionId(): string {
815
+ let ret = window.sessionStorage?.getItem('xhSessionId');
816
+ if (!ret) {
817
+ ret = new ShortUniqueId({length: 8}).rnd();
818
+ window.sessionStorage?.setItem('xhSessionId', ret);
819
+ }
820
+ return ret;
821
+ }
797
822
  }
798
823
 
799
824
  /** The app-wide singleton instance. */
@@ -12,7 +12,7 @@ import {Icon} from '@xh/hoist/icon';
12
12
  import {menu, menuDivider, menuItem} from '@xh/hoist/kit/blueprint';
13
13
  import {pluralize} from '@xh/hoist/utils/js';
14
14
  import {Dictionary} from 'express-serve-static-core';
15
- import {each, filter, groupBy, isEmpty, orderBy, some, startCase} from 'lodash';
15
+ import {each, filter, groupBy, isEmpty, isFunction, orderBy, some, startCase} from 'lodash';
16
16
  import {ReactNode} from 'react';
17
17
  import {ViewManagerLocalModel} from './ViewManagerLocalModel';
18
18
 
@@ -162,12 +162,14 @@ function viewMenuItem(view: ViewInfo, model: ViewManagerModel): ReactNode {
162
162
  if (!view.isOwned && view.owner) title.push(view.owner);
163
163
  if (view.description) title.push(view.description);
164
164
 
165
- return menuItem({
166
- className: 'xh-view-manager__menu-item',
167
- key: view.token,
168
- text: view.name,
169
- title: title.join(' | '),
170
- icon,
171
- onClick: () => model.selectViewAsync(view).catchDefault()
172
- });
165
+ return isFunction(model.viewMenuItemFn)
166
+ ? model.viewMenuItemFn(view, model)
167
+ : menuItem({
168
+ className: 'xh-view-manager__menu-item',
169
+ key: view.token,
170
+ text: view.name,
171
+ title: title.join(' | '),
172
+ icon,
173
+ onClick: () => model.selectViewAsync(view).catchDefault()
174
+ });
173
175
  }
@@ -4,11 +4,11 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {isLocalDate, LocalDate} from '@xh/hoist/utils/datetime';
8
- import {defaults, isString} from 'lodash';
7
+ import {DAYS, isLocalDate, LocalDate} from '@xh/hoist/utils/datetime';
8
+ import {defaults, isFinite, isString} from 'lodash';
9
9
  import moment from 'moment';
10
10
  import {ReactNode} from 'react';
11
- import {DateLike} from '../core/types/Types';
11
+ import {DateLike, PlainObject} from '../core/types/Types';
12
12
  import {fmtSpan, FormatOptions} from './FormatMisc';
13
13
  import {createRenderer} from './FormatUtils';
14
14
  import {saveOriginal} from './impl/Utils';
@@ -148,6 +148,48 @@ export function fmtCompactDate(v: DateLike, opts?: CompactDateFormatOptions) {
148
148
  return fmtDate(v, dateOpts);
149
149
  }
150
150
 
151
+ export interface TimestampReplacerConfig {
152
+ /**
153
+ * Suffixes used to identify keys that may hold timestamps.
154
+ * Defaults to ['time', 'date', 'timestamp']
155
+ */
156
+ suffixes?: string[];
157
+
158
+ /**
159
+ * Format for replaced timestamp.
160
+ * Defaults to 'MMM DD HH:mm:ss.SSS'
161
+ */
162
+ format?: string;
163
+ }
164
+
165
+ /**
166
+ * Replace timestamps in an Object with formatted strings.
167
+ */
168
+ export function withFormattedTimestamps(
169
+ obj: PlainObject,
170
+ config: TimestampReplacerConfig = {}
171
+ ): PlainObject {
172
+ return JSON.parse(JSON.stringify(obj, timestampReplacer(config)));
173
+ }
174
+
175
+ /**
176
+ * Create a replacer, suitable for JSON.stringify, that will replace timestamps with
177
+ * formatted strings.
178
+ */
179
+ export function timestampReplacer(
180
+ config: TimestampReplacerConfig = {}
181
+ ): (k: string, v: any) => any {
182
+ const suffixes = config.suffixes ?? ['time', 'date', 'timestamp'],
183
+ fmt = 'MMM DD HH:mm:ss.SSS';
184
+ return (k: string, v: any) => {
185
+ return suffixes.some(s => k.toLowerCase().endsWith(s.toLowerCase())) &&
186
+ isFinite(v) &&
187
+ v > Date.now() - 25 * 365 * DAYS // heuristic to avoid catching smaller ms ranges
188
+ ? fmtDateTime(v, {fmt})
189
+ : v;
190
+ };
191
+ }
192
+
151
193
  export const dateRenderer = createRenderer(fmtDate),
152
194
  dateTimeRenderer = createRenderer(fmtDateTime),
153
195
  dateTimeSecRenderer = createRenderer(fmtDateTimeSec),
@@ -5,8 +5,9 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {span} from '@xh/hoist/cmp/layout';
8
- import {capitalize, isNil, kebabCase, map} from 'lodash';
8
+ import {capitalize, isNil, isString, kebabCase, map} from 'lodash';
9
9
  import {CSSProperties, ReactNode} from 'react';
10
+ import {PlainObject} from '@xh/hoist/core';
10
11
 
11
12
  export interface FormatOptions<T = any> {
12
13
  /** Display value for null values. */
@@ -63,11 +64,12 @@ export interface JSONFormatOptions {
63
64
  }
64
65
 
65
66
  /**
66
- * Pretty-print a JSON string, adding line breaks and indentation.
67
+ * Pretty-print a JSON string or (JSON Object), adding line breaks and indentation.
67
68
  */
68
- export function fmtJson(str: string, opts?: JSONFormatOptions): string {
69
+ export function fmtJson(v: string | PlainObject, opts?: JSONFormatOptions): string {
69
70
  const {replacer = undefined, space = 2} = opts ?? {};
70
- return isNil(str) ? '' : JSON.stringify(JSON.parse(str), replacer, space);
71
+ if (isString(v)) v = JSON.parse(v);
72
+ return isNil(v) ? '' : JSON.stringify(v, replacer, space);
71
73
  }
72
74
 
73
75
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "72.3.0",
3
+ "version": "72.4.0",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",
package/security/Types.ts CHANGED
@@ -31,8 +31,8 @@ export interface TelemetryResults {
31
31
 
32
32
  /** Aggregated telemetry results for a single type of event. */
33
33
  export interface TelemetryEventResults {
34
- firstTime: Date;
35
- lastTime: Date;
34
+ firstTime: number;
35
+ lastTime: number;
36
36
  successCount: number;
37
37
  failureCount: number;
38
38
  /** Timing info (in ms) for event instances reported with duration. */
@@ -43,7 +43,7 @@ export interface TelemetryEventResults {
43
43
  worst: number;
44
44
  };
45
45
  lastFailure?: {
46
- time: Date;
46
+ time: number;
47
47
  duration: number;
48
48
  code: string;
49
49
  name: string;
@@ -14,9 +14,10 @@ import {
14
14
  PopupRequest,
15
15
  SilentRequest
16
16
  } from '@azure/msal-browser';
17
- import {XH} from '@xh/hoist/core';
17
+ import {AppState, PlainObject, XH} from '@xh/hoist/core';
18
18
  import {Token} from '@xh/hoist/security/Token';
19
19
  import {logDebug, logError, logInfo, logWarn, mergeDeep, throwIf} from '@xh/hoist/utils/js';
20
+ import {withFormattedTimestamps} from '@xh/hoist/format';
20
21
  import {flatMap, union, uniq} from 'lodash';
21
22
  import {BaseOAuthClient, BaseOAuthClientConfig} from '../BaseOAuthClient';
22
23
  import {AccessTokenSpec, TelemetryResults, TokenMap} from '../Types';
@@ -266,9 +267,13 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
266
267
  //------------------------
267
268
  // Telemetry
268
269
  //------------------------
270
+ getFormattedTelemetry(): PlainObject {
271
+ return withFormattedTimestamps(this.telemetryResults);
272
+ }
273
+
269
274
  enableTelemetry(): void {
270
275
  if (this._telemetryCbHandle) {
271
- this.logInfo('Telemetry already enabled', this.telemetryResults);
276
+ this.logInfo('Telemetry already enabled', this.getFormattedTelemetry());
272
277
  return;
273
278
  }
274
279
 
@@ -279,7 +284,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
279
284
  try {
280
285
  const {events} = this.telemetryResults,
281
286
  {name, startTimeMs, durationMs, success, errorName, errorCode} = e,
282
- eTime = startTimeMs ? new Date(startTimeMs) : new Date();
287
+ eTime = startTimeMs ?? Date.now();
283
288
 
284
289
  const eResult = (events[name] ??= {
285
290
  firstTime: eTime,
@@ -316,15 +321,10 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
316
321
  });
317
322
  });
318
323
 
319
- // Ask TrackService to include in background health check report, if enabled on that service.
320
- // Handle TrackService not yet initialized (common, this client likely initialized before.)
324
+ // Wait for clientHealthService (this client likely initialized during earlier AUTHENTICATING.)
321
325
  this.addReaction({
322
- when: () => XH.appIsRunning,
323
- run: () =>
324
- XH.trackService.addClientHealthReportSource(
325
- 'msalClient',
326
- () => this.telemetryResults
327
- )
326
+ when: () => XH.appState === AppState.INITIALIZING_APP,
327
+ run: () => XH.clientHealthService.addSource('msalClient', () => this.telemetryResults)
328
328
  });
329
329
 
330
330
  this.logInfo('Telemetry enabled');
@@ -339,8 +339,8 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
339
339
  this.client.removePerformanceCallback(this._telemetryCbHandle);
340
340
  this._telemetryCbHandle = null;
341
341
 
342
- XH.trackService.removeClientHealthReportSource('msalClient');
343
- this.logInfo('Telemetry disabled', this.telemetryResults);
342
+ XH.clientHealthService.removeSource('msalClient');
343
+ this.logInfo('Telemetry disabled', this.getFormattedTelemetry());
344
344
  }
345
345
 
346
346
  //------------------------
@@ -0,0 +1,165 @@
1
+ /*
2
+ * This file belongs to Hoist, an application development toolkit
3
+ * developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
4
+ *
5
+ * Copyright © 2025 Extremely Heavy Industries Inc.
6
+ */
7
+ import {HoistService, PageState, PlainObject, XH} from '@xh/hoist/core';
8
+ import {Timer} from '@xh/hoist/utils/async';
9
+ import {MINUTES} from '@xh/hoist/utils/datetime';
10
+ import {withFormattedTimestamps} from '@xh/hoist/format';
11
+ import {pick, round} from 'lodash';
12
+
13
+ /**
14
+ * Service for gathering data about client health.
15
+ *
16
+ * Hoist sends this data once on application load, and can be configured to send
17
+ * it at regularly scheduled intervals. Configure via soft-config property
18
+ * 'xhActivityTracking.clientHealthReport'.
19
+ */
20
+ export class ClientHealthService extends HoistService {
21
+ static instance: ClientHealthService;
22
+
23
+ private sources: Map<string, () => any> = new Map();
24
+
25
+ override async initAsync() {
26
+ const {clientHealthReport} = XH.trackService.conf;
27
+ Timer.create({
28
+ runFn: () => this.sendReport(),
29
+ interval: clientHealthReport.intervalMins * MINUTES,
30
+ delay: true
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Main entry point. Return a default report of client health.
36
+ */
37
+ getReport(): ClientHealthReport {
38
+ return {
39
+ general: this.getGeneral(),
40
+ memory: this.getMemory(),
41
+ connection: this.getConnection(),
42
+ ...this.getCustom()
43
+ };
44
+ }
45
+
46
+ /** Get report, suitable for viewing in console. **/
47
+ getFormattedReport(): PlainObject {
48
+ return withFormattedTimestamps(this.getReport());
49
+ }
50
+
51
+ /**
52
+ * Register a new source for client health report data. No-op if background health report is
53
+ * not generally enabled via `xhActivityTrackingConfig.clientHealthReport.intervalMins`.
54
+ *
55
+ * @param key - key under which to report the data - can be used to remove this source later.
56
+ * @param callback - function returning serializable to include with each report.
57
+ */
58
+ addSource(key: string, callback: () => any) {
59
+ this.sources.set(key, callback);
60
+ }
61
+
62
+ /** Unregister a previously-enabled source for client health report data. */
63
+ removeSource(key: string) {
64
+ this.sources.delete(key);
65
+ }
66
+
67
+ // -----------------------------------
68
+ // Generate individual report sections
69
+ //------------------------------------
70
+ getGeneral(): GeneralData {
71
+ const startTime = XH.appContainerModel.appStateModel.loadStarted,
72
+ elapsedMins = (ts: number) => round((Date.now() - ts) / 60_000, 1);
73
+
74
+ return {
75
+ startTime,
76
+ durationMins: elapsedMins(startTime),
77
+ idleMins: elapsedMins(XH.lastActivityMs),
78
+ pageState: XH.pageState,
79
+ webSocket: XH.webSocketService.channelKey
80
+ };
81
+ }
82
+
83
+ getConnection(): ConnectionData {
84
+ const nav = window.navigator as any;
85
+ if (!nav.connection) return null;
86
+ return pick(nav.connection, ['downlink', 'effectiveType', 'rtt']);
87
+ }
88
+
89
+ getMemory(): MemoryData {
90
+ const perf = window.performance as any;
91
+ if (!perf?.memory) return null;
92
+
93
+ const ret: MemoryData = {modelCount: XH.getModels().length};
94
+ ['jsHeapSizeLimit', 'totalJSHeapSize', 'usedJSHeapSize'].forEach(key => {
95
+ const raw = perf.memory[key];
96
+ if (raw) ret[key] = round(raw / 1024 / 1024); // convert to MB
97
+ });
98
+
99
+ const {jsHeapSizeLimit: limit, usedJSHeapSize: used} = ret;
100
+ if (limit && used) {
101
+ ret.usedPctLimit = round((used / limit) * 100);
102
+ }
103
+
104
+ return ret;
105
+ }
106
+
107
+ getCustom(): PlainObject {
108
+ const ret = {};
109
+ this.sources.forEach((cb, k) => {
110
+ try {
111
+ ret[k] = cb();
112
+ } catch (e) {
113
+ ret[k] = `Error: ${e.message}`;
114
+ this.logWarn(`Error running client health report callback for [${k}]`, e);
115
+ }
116
+ });
117
+ return ret;
118
+ }
119
+
120
+ //------------------
121
+ // Implementation
122
+ //------------------
123
+ private sendReport() {
124
+ const {intervalMins, ...rest} = XH.trackService.conf.clientHealthReport ?? {};
125
+
126
+ XH.track({
127
+ category: 'App',
128
+ message: 'Submitted health report',
129
+ ...rest,
130
+ data: {
131
+ clientId: XH.clientId,
132
+ sessionId: XH.sessionId,
133
+ ...this.getReport()
134
+ }
135
+ });
136
+ }
137
+ }
138
+
139
+ export interface GeneralData {
140
+ startTime: number;
141
+ durationMins: number;
142
+ idleMins: number;
143
+ pageState: PageState;
144
+ webSocket: string;
145
+ }
146
+
147
+ export interface ConnectionData {
148
+ downlink: number;
149
+ effectiveType: string;
150
+ rtt: number;
151
+ }
152
+
153
+ export interface MemoryData {
154
+ modelCount: number;
155
+ usedPctLimit?: number;
156
+ jsHeapSizeLimit?: number;
157
+ totalJSHeapSize?: number;
158
+ usedJSHeapSize?: number;
159
+ }
160
+
161
+ export interface ClientHealthReport {
162
+ general: GeneralData;
163
+ connection: ConnectionData;
164
+ memory: MemoryData;
165
+ }
@@ -5,11 +5,10 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {HoistService, PlainObject, TrackOptions, XH} from '@xh/hoist/core';
8
- import {Timer} from '@xh/hoist/utils/async';
9
- import {MINUTES, SECONDS} from '@xh/hoist/utils/datetime';
8
+ import {SECONDS} from '@xh/hoist/utils/datetime';
10
9
  import {isOmitted} from '@xh/hoist/utils/impl';
11
- import {debounced, getClientDeviceInfo, stripTags, withDefault} from '@xh/hoist/utils/js';
12
- import {isEmpty, isNil, isString, round} from 'lodash';
10
+ import {debounced, stripTags, withDefault} from '@xh/hoist/utils/js';
11
+ import {isEmpty, isNil, isString} from 'lodash';
13
12
 
14
13
  /**
15
14
  * Primary service for tracking any activity that an application's admins want to track.
@@ -19,21 +18,10 @@ import {isEmpty, isNil, isString, round} from 'lodash';
19
18
  export class TrackService extends HoistService {
20
19
  static instance: TrackService;
21
20
 
22
- private clientHealthReportSources: Map<string, () => any> = new Map();
23
21
  private oncePerSessionSent = new Map();
24
22
  private pending: PlainObject[] = [];
25
23
 
26
24
  override async initAsync() {
27
- const {clientHealthReport} = this.conf;
28
- if (clientHealthReport?.intervalMins > 0) {
29
- Timer.create({
30
- runFn: () => this.sendClientHealthReport(),
31
- interval: clientHealthReport.intervalMins,
32
- intervalUnits: MINUTES,
33
- delay: true
34
- });
35
- }
36
-
37
25
  window.addEventListener('beforeunload', () => this.pushPendingAsync());
38
26
  }
39
27
 
@@ -102,22 +90,6 @@ export class TrackService extends HoistService {
102
90
  this.pushPendingBuffered();
103
91
  }
104
92
 
105
- /**
106
- * Register a new source for client health report data. No-op if background health report is
107
- * not generally enabled via `xhActivityTrackingConfig.clientHealthReport.intervalMins`.
108
- *
109
- * @param key - key under which to report the data - can be used to remove this source later.
110
- * @param callback - function returning serializable to include with each report.
111
- */
112
- addClientHealthReportSource(key: string, callback: () => any) {
113
- this.clientHealthReportSources.set(key, callback);
114
- }
115
-
116
- /** Unregister a previously-enabled source for client health report data. */
117
- removeClientHealthReportSource(key: string) {
118
- this.clientHealthReportSources.delete(key);
119
- }
120
-
121
93
  //------------------
122
94
  // Implementation
123
95
  //------------------
@@ -176,42 +148,6 @@ export class TrackService extends HoistService {
176
148
 
177
149
  this.logInfo(...consoleMsgs);
178
150
  }
179
-
180
- private sendClientHealthReport() {
181
- const {
182
- intervalMins,
183
- severity: defaultSeverity,
184
- ...rest
185
- } = this.conf.clientHealthReport ?? {},
186
- {loadStarted} = XH.appContainerModel.appStateModel;
187
-
188
- const data = {
189
- session: {
190
- started: loadStarted,
191
- durationMins: round((Date.now() - loadStarted) / 60_000, 1)
192
- },
193
- ...getClientDeviceInfo()
194
- };
195
-
196
- let severity = defaultSeverity ?? 'INFO';
197
- this.clientHealthReportSources.forEach((cb, k) => {
198
- try {
199
- data[k] = cb();
200
- if (data[k]?.severity === 'WARN') severity = 'WARN';
201
- } catch (e) {
202
- data[k] = `Error: ${e.message}`;
203
- this.logWarn(`Error running client health report callback for [${k}]`, e);
204
- }
205
- });
206
-
207
- this.track({
208
- category: 'App',
209
- message: 'Submitted health report',
210
- severity,
211
- ...rest,
212
- data
213
- });
214
- }
215
151
  }
216
152
 
217
153
  interface ActivityTrackingConfig {
package/svc/index.ts CHANGED
@@ -19,5 +19,6 @@ export * from './JsonBlobService';
19
19
  export * from './PrefService';
20
20
  export * from './TrackService';
21
21
  export * from './WebSocketService';
22
+ export * from './ClientHealthService';
22
23
  export * from './storage/LocalStorageService';
23
24
  export * from './storage/SessionStorageService';