@xh/hoist 73.0.0-SNAPSHOT.1744315927263 → 73.0.0-SNAPSHOT.1744386127895

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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  * Added option from the Admin Console > Websockets tab to request a client health report from any
8
8
  connected clients.
9
+ * Enabled telemetry reporting from `WebSocketService`.
10
+ * Updated `MenuItem.actionFn()` to receive the click event as an additional argument.
9
11
 
10
12
  ## v72.4.0 - 2025-04-09
11
13
 
@@ -5,17 +5,17 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {exportFilename} from '@xh/hoist/admin/AdminUtils';
8
- import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
8
+ import * as Col from '@xh/hoist/admin/columns';
9
9
  import {FilterChooserModel} from '@xh/hoist/cmp/filter';
10
10
  import {FormModel} from '@xh/hoist/cmp/form';
11
11
  import {GridModel, TreeStyle} from '@xh/hoist/cmp/grid';
12
+ import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
12
13
  import {HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
13
14
  import {Cube, CubeFieldSpec, FieldSpec} from '@xh/hoist/data';
14
15
  import {fmtNumber} from '@xh/hoist/format';
15
16
  import {action, computed, makeObservable} from '@xh/hoist/mobx';
16
17
  import {LocalDate} from '@xh/hoist/utils/datetime';
17
- import * as Col from '@xh/hoist/admin/columns';
18
- import {isEmpty, round} from 'lodash';
18
+ import {compact, isEmpty, round} from 'lodash';
19
19
  import moment from 'moment';
20
20
 
21
21
  export const PERSIST_ACTIVITY = {localStorageKey: 'xhAdminActivityState'};
@@ -109,14 +109,13 @@ export class ActivityTrackingModel extends HoistModel {
109
109
  ] as CubeFieldSpec[]
110
110
  });
111
111
 
112
- const enableValues = true;
113
112
  this.filterChooserModel = new FilterChooserModel({
114
113
  fieldSpecs: [
115
- {field: 'category', enableValues},
114
+ {field: 'category'},
116
115
  {field: 'correlationId'},
117
- {field: 'username', displayName: 'User', enableValues},
118
- {field: 'device', enableValues},
119
- {field: 'browser', enableValues},
116
+ {field: 'username', displayName: 'User'},
117
+ {field: 'device'},
118
+ {field: 'browser'},
120
119
  {
121
120
  field: 'elapsed',
122
121
  valueRenderer: v => {
@@ -330,12 +329,21 @@ export class ActivityTrackingModel extends HoistModel {
330
329
  }
331
330
 
332
331
  private async loadLookupsAsync() {
333
- const lookups = await XH.fetchJson({url: 'trackLogAdmin/lookups'});
334
-
335
- this.filterChooserModel.fieldSpecs.forEach(spec => {
336
- const {field} = spec;
337
- if (lookups[field]) spec.values = lookups[field];
338
- });
332
+ try {
333
+ const lookups = await XH.fetchJson({url: 'trackLogAdmin/lookups'});
334
+ this.filterChooserModel.fieldSpecs.forEach(spec => {
335
+ const {field} = spec,
336
+ lookup = lookups[field] ? compact(lookups[field]) : null;
337
+
338
+ if (!isEmpty(lookup)) {
339
+ spec.values = lookup;
340
+ spec.enableValues = true;
341
+ spec.hasExplicitValues = true;
342
+ }
343
+ });
344
+ } catch (e) {
345
+ XH.handleException(e, {title: 'Error loading lookups for filtering'});
346
+ }
339
347
  }
340
348
 
341
349
  @computed
@@ -1,7 +1,7 @@
1
- import { GroupingChooserModel } from '@xh/hoist/cmp/grouping';
2
1
  import { FilterChooserModel } from '@xh/hoist/cmp/filter';
3
2
  import { FormModel } from '@xh/hoist/cmp/form';
4
3
  import { GridModel } from '@xh/hoist/cmp/grid';
4
+ import { GroupingChooserModel } from '@xh/hoist/cmp/grouping';
5
5
  import { HoistModel, LoadSpec } from '@xh/hoist/core';
6
6
  import { Cube } from '@xh/hoist/data';
7
7
  import { LocalDate } from '@xh/hoist/utils/datetime';
@@ -1,5 +1,5 @@
1
1
  import { RuleLike } from '@xh/hoist/data';
2
- import { ReactElement, ReactNode } from 'react';
2
+ import { MouseEvent, ReactElement, ReactNode } from 'react';
3
3
  import { LoadSpec } from '../load';
4
4
  import { Intent, PlainObject, Thunkable } from './Types';
5
5
  /**
@@ -226,7 +226,7 @@ export interface MenuItem {
226
226
  /** Css class name to be added when rendering the menu item. */
227
227
  className?: string;
228
228
  /** Executed when the user clicks the menu item. */
229
- actionFn?: () => void;
229
+ actionFn?: (e: MouseEvent | PointerEvent) => void;
230
230
  /** Executed before the item is shown. Use to adjust properties dynamically. */
231
231
  prepareFn?: (me: MenuItem) => void;
232
232
  /** Child menu items. */
@@ -12,35 +12,3 @@ export interface AccessTokenSpec {
12
12
  scopes: string[];
13
13
  }
14
14
  export type TokenMap = Record<string, Token>;
15
- /** Aggregated telemetry results, produced by {@link MsalClient} when enabled via config. */
16
- export interface TelemetryResults {
17
- /** Stats across all events */
18
- summary: {
19
- successCount: number;
20
- failureCount: number;
21
- maxDuration: number;
22
- lastFailureTime: number;
23
- };
24
- /** Stats by event type */
25
- events: Record<string, TelemetryEventResults>;
26
- }
27
- /** Aggregated telemetry results for a single type of event. */
28
- export interface TelemetryEventResults {
29
- firstTime: number;
30
- lastTime: number;
31
- successCount: number;
32
- failureCount: number;
33
- /** Timing info (in ms) for event instances reported with duration. */
34
- duration?: {
35
- count: number;
36
- total: number;
37
- average: number;
38
- max: number;
39
- };
40
- lastFailure?: {
41
- time: number;
42
- duration: number;
43
- code: string;
44
- name: string;
45
- };
46
- }
@@ -3,7 +3,7 @@ import { LogLevel } from '@azure/msal-browser';
3
3
  import { PlainObject } from '@xh/hoist/core';
4
4
  import { Token } from '@xh/hoist/security/Token';
5
5
  import { BaseOAuthClient, BaseOAuthClientConfig } from '../BaseOAuthClient';
6
- import { AccessTokenSpec, TelemetryResults, TokenMap } from '../Types';
6
+ import { AccessTokenSpec, TokenMap } from '../Types';
7
7
  export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
8
8
  /**
9
9
  * Authority for your organization's tenant: `https://login.microsoftonline.com/[tenantId]`.
@@ -19,8 +19,7 @@ export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
19
19
  domainHint?: string;
20
20
  /**
21
21
  * True to enable support for built-in telemetry provided by this class's internal MSAL client.
22
- * Captured performance events will be summarized via {@link telemetryResults}.
23
- * See https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/performance.md
22
+ * Captured performance events will be summarized as {@link MsalClientTelemetry}.
24
23
  */
25
24
  enableTelemetry?: boolean;
26
25
  /**
@@ -88,7 +87,7 @@ export declare class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTo
88
87
  private account;
89
88
  private initialTokenLoad;
90
89
  /** Enable telemetry via `enableTelemetry` ctor config, or via {@link enableTelemetry}. */
91
- telemetryResults: TelemetryResults;
90
+ telemetry: MsalClientTelemetry;
92
91
  private _telemetryCbHandle;
93
92
  constructor(config: MsalClientConfig);
94
93
  protected doInitAsync(): Promise<TokenMap>;
@@ -108,3 +107,40 @@ export declare class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTo
108
107
  private get refreshOffsetArgs();
109
108
  private noteUserAuthenticated;
110
109
  }
110
+ /**
111
+ * Telemetry produced by this client (if enabled) + included in {@link ClientHealthService}
112
+ * reporting. Leverages MSAL's opt-in support for emitting performance events.
113
+ * See https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/performance.md
114
+ */
115
+ interface MsalClientTelemetry {
116
+ /** Stats across all events */
117
+ summary: {
118
+ successCount: number;
119
+ failureCount: number;
120
+ maxDuration: number;
121
+ lastFailureTime: number;
122
+ };
123
+ /** Stats by event type */
124
+ events: Record<string, MsalEventTelemetry>;
125
+ }
126
+ /** Aggregated telemetry results for a single type of event. */
127
+ interface MsalEventTelemetry {
128
+ firstTime: number;
129
+ lastTime: number;
130
+ successCount: number;
131
+ failureCount: number;
132
+ /** Timing info (in ms) for event instances reported with duration. */
133
+ duration?: {
134
+ count: number;
135
+ total: number;
136
+ average: number;
137
+ max: number;
138
+ };
139
+ lastFailure?: {
140
+ time: number;
141
+ duration: number;
142
+ code: string;
143
+ name: string;
144
+ };
145
+ }
146
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { HoistService, PageState, PlainObject } from '@xh/hoist/core';
2
+ import { WebSocketTelemetry } from '@xh/hoist/svc/WebSocketService';
2
3
  /**
3
4
  * Service for gathering data about the current state and health of the client app, for submission
4
5
  * to the server or review on the console during interactive troubleshooting.
@@ -42,7 +43,6 @@ export interface GeneralData {
42
43
  durationMins: number;
43
44
  idleMins: number;
44
45
  pageState: PageState;
45
- webSocket: string;
46
46
  }
47
47
  export interface ConnectionData {
48
48
  downlink: number;
@@ -60,4 +60,5 @@ export interface ClientHealthReport {
60
60
  general: GeneralData;
61
61
  connection: ConnectionData;
62
62
  memory: MemoryData;
63
+ webSockets: WebSocketTelemetry;
63
64
  }
@@ -1,4 +1,4 @@
1
- import { HoistService } from '@xh/hoist/core';
1
+ import { HoistService, PlainObject } from '@xh/hoist/core';
2
2
  /**
3
3
  * Establishes and maintains a websocket connection to the Hoist server, if enabled via `AppSpec`.
4
4
  *
@@ -28,6 +28,8 @@ export declare class WebSocketService extends HoistService {
28
28
  readonly REG_SUCCESS_TOPIC = "xhRegistrationSuccess";
29
29
  readonly FORCE_APP_SUSPEND_TOPIC = "xhForceAppSuspend";
30
30
  readonly REQ_CLIENT_HEALTH_RPT_TOPIC = "xhRequestClientHealthReport";
31
+ /** True if WebSockets generally enabled - set statically in code via {@link AppSpec}. */
32
+ enabled: boolean;
31
33
  /** Unique channel assigned by server upon successful connection. */
32
34
  channelKey: string;
33
35
  /** Last time a message was received, including heartbeat messages. */
@@ -36,10 +38,10 @@ export declare class WebSocketService extends HoistService {
36
38
  get connected(): boolean;
37
39
  /** Set to true to log all sent/received messages - very chatty. */
38
40
  logMessages: boolean;
41
+ telemetry: WebSocketTelemetry;
39
42
  private _timer;
40
43
  private _socket;
41
44
  private _subsByTopic;
42
- enabled: boolean;
43
45
  constructor();
44
46
  initAsync(): Promise<void>;
45
47
  /**
@@ -62,23 +64,24 @@ export declare class WebSocketService extends HoistService {
62
64
  * Send a message back to the server via the connected websocket.
63
65
  */
64
66
  sendMessage(message: WebSocketMessage): void;
65
- connect(): void;
66
- disconnect(): void;
67
- heartbeatOrReconnect(): void;
68
- private onServerInstanceChange;
69
67
  shutdown(): void;
68
+ getFormattedTelemetry(): PlainObject;
69
+ private connect;
70
+ private disconnect;
71
+ private heartbeatOrReconnect;
72
+ private onServerInstanceChange;
70
73
  onOpen(ev: any): void;
71
74
  onClose(ev: any): void;
72
75
  onError(ev: any): void;
73
76
  onMessage(rawMsg: MessageEvent): void;
74
- notifySubscribers(message: any): void;
75
- getSubsForTopic(topic: any): WebSocketSubscription[];
76
- updateConnectedStatus(): void;
77
- installChannelKey(key: any): void;
78
- updateLastMessageTime(): void;
79
- buildWebSocketUrl(): string;
80
- showTestMessageAlert(message: any): void;
81
- maybeLogMessage(...args: any[]): void;
77
+ private notifySubscribers;
78
+ private getSubsForTopic;
79
+ private updateConnectedStatus;
80
+ private installChannelKey;
81
+ private updateLastMessageTime;
82
+ private buildWebSocketUrl;
83
+ private maybeLogMessage;
84
+ private noteTelemetryEvent;
82
85
  }
83
86
  /**
84
87
  * Wrapper class to encapsulate and manage a subscription to messages for a given topic + handler.
@@ -94,3 +97,21 @@ export interface WebSocketMessage {
94
97
  topic: string;
95
98
  data?: any;
96
99
  }
100
+ /** Telemetry collected by this service + included in {@link ClientHealthService} reporting. */
101
+ export interface WebSocketTelemetry {
102
+ channelKey: string;
103
+ subscriptionCount: number;
104
+ events: {
105
+ connOpened?: WebSocketEventTelemetry;
106
+ connClosed?: WebSocketEventTelemetry;
107
+ connError?: WebSocketEventTelemetry;
108
+ msgReceived?: WebSocketEventTelemetry;
109
+ msgSent?: WebSocketEventTelemetry;
110
+ heartbeatReconnectAttempt?: WebSocketEventTelemetry;
111
+ instanceChangeReconnectAttempt?: WebSocketEventTelemetry;
112
+ };
113
+ }
114
+ export interface WebSocketEventTelemetry {
115
+ count: number;
116
+ lastTime: number;
117
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import {RuleLike} from '@xh/hoist/data';
9
- import {ReactElement, ReactNode} from 'react';
9
+ import {MouseEvent, ReactElement, ReactNode} from 'react';
10
10
  import {LoadSpec} from '../load';
11
11
  import {Intent, PlainObject, Thunkable} from './Types';
12
12
 
@@ -284,7 +284,7 @@ export interface MenuItem {
284
284
  className?: string;
285
285
 
286
286
  /** Executed when the user clicks the menu item. */
287
- actionFn?: () => void;
287
+ actionFn?: (e: MouseEvent | PointerEvent) => void;
288
288
 
289
289
  /** Executed before the item is shown. Use to adjust properties dynamically. */
290
290
  prepareFn?: (me: MenuItem) => void;
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {HoistBase} from '@xh/hoist/core';
8
8
  import {Field, Store, FieldFilter, FieldType, genDisplayName, View} from '@xh/hoist/data';
9
- import {isEmpty} from 'lodash';
9
+ import {compact, isArray, isEmpty} from 'lodash';
10
10
  import {FieldFilterOperator} from './Types';
11
11
 
12
12
  export interface BaseFilterFieldSpecConfig {
@@ -72,7 +72,11 @@ export abstract class BaseFilterFieldSpec extends HoistBase {
72
72
  this.displayName = displayName ?? sourceField?.displayName ?? genDisplayName(field);
73
73
  this.ops = this.parseOperators(ops);
74
74
  this.forceSelection = forceSelection ?? false;
75
- this.values = values ?? (this.isBoolFieldType ? [true, false] : null);
75
+ this.values = isArray(values)
76
+ ? compact(values)
77
+ : this.isBoolFieldType
78
+ ? [true, false]
79
+ : null;
76
80
  this.hasExplicitValues = !isEmpty(this.values);
77
81
  this.enableValues = this.hasExplicitValues || (enableValues ?? this.isEnumerableByDefault);
78
82
  }
@@ -202,7 +202,7 @@ function parseMenuItems(items: MenuItemLike[]): ReactNode[] {
202
202
  icon: item.icon,
203
203
  intent: item.intent,
204
204
  className: item.className,
205
- onClick: actionFn ? () => wait().then(actionFn) : null, // do async to allow menu to close
205
+ onClick: actionFn ? e => wait().then(() => actionFn(e)) : null, // do async to allow menu to close
206
206
  disabled: item.disabled
207
207
  };
208
208
 
@@ -70,7 +70,7 @@ function parseItems(items: MenuItemLike[]): ReactNode[] {
70
70
  icon: item.icon,
71
71
  intent: item.intent,
72
72
  className: item.className,
73
- onClick: item.actionFn ? () => wait().then(item.actionFn) : null, // do async to allow menu to close
73
+ onClick: item.actionFn ? e => wait().then(() => item.actionFn(e)) : null, // do async to allow menu to close
74
74
  popoverProps: {usePortal: true},
75
75
  disabled: item.disabled,
76
76
  items
@@ -98,9 +98,9 @@ class LocalMenuModel extends HoistModel {
98
98
  omit: hidden,
99
99
  onTouchStart: () => (this.pressedIdx = idx),
100
100
  onTouchEnd: () => (this.pressedIdx = null),
101
- onClick: () => {
101
+ onClick: e => {
102
102
  this.pressedIdx = null;
103
- if (actionFn) actionFn();
103
+ if (actionFn) actionFn(e);
104
104
  onDismiss();
105
105
  }
106
106
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "73.0.0-SNAPSHOT.1744315927263",
3
+ "version": "73.0.0-SNAPSHOT.1744386127895",
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
@@ -22,37 +22,3 @@ export interface AccessTokenSpec {
22
22
  }
23
23
 
24
24
  export type TokenMap = Record<string, Token>;
25
-
26
- /** Aggregated telemetry results, produced by {@link MsalClient} when enabled via config. */
27
- export interface TelemetryResults {
28
- /** Stats across all events */
29
- summary: {
30
- successCount: number;
31
- failureCount: number;
32
- maxDuration: number;
33
- lastFailureTime: number;
34
- };
35
- /** Stats by event type */
36
- events: Record<string, TelemetryEventResults>;
37
- }
38
-
39
- /** Aggregated telemetry results for a single type of event. */
40
- export interface TelemetryEventResults {
41
- firstTime: number;
42
- lastTime: number;
43
- successCount: number;
44
- failureCount: number;
45
- /** Timing info (in ms) for event instances reported with duration. */
46
- duration?: {
47
- count: number;
48
- total: number;
49
- average: number;
50
- max: number;
51
- };
52
- lastFailure?: {
53
- time: number;
54
- duration: number;
55
- code: string;
56
- name: string;
57
- };
58
- }
@@ -20,7 +20,7 @@ import {logDebug, logError, logInfo, logWarn, mergeDeep, throwIf} from '@xh/hois
20
20
  import {withFormattedTimestamps} from '@xh/hoist/format';
21
21
  import {flatMap, union, uniq} from 'lodash';
22
22
  import {BaseOAuthClient, BaseOAuthClientConfig} from '../BaseOAuthClient';
23
- import {AccessTokenSpec, TelemetryResults, TokenMap} from '../Types';
23
+ import {AccessTokenSpec, TokenMap} from '../Types';
24
24
 
25
25
  export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
26
26
  /**
@@ -39,8 +39,7 @@ export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
39
39
 
40
40
  /**
41
41
  * True to enable support for built-in telemetry provided by this class's internal MSAL client.
42
- * Captured performance events will be summarized via {@link telemetryResults}.
43
- * See https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/performance.md
42
+ * Captured performance events will be summarized as {@link MsalClientTelemetry}.
44
43
  */
45
44
  enableTelemetry?: boolean;
46
45
 
@@ -115,7 +114,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
115
114
  private initialTokenLoad: boolean;
116
115
 
117
116
  /** Enable telemetry via `enableTelemetry` ctor config, or via {@link enableTelemetry}. */
118
- telemetryResults: TelemetryResults = null;
117
+ telemetry: MsalClientTelemetry = null;
119
118
  private _telemetryCbHandle: string = null;
120
119
 
121
120
  constructor(config: MsalClientConfig) {
@@ -268,7 +267,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
268
267
  // Telemetry
269
268
  //------------------------
270
269
  getFormattedTelemetry(): PlainObject {
271
- return withFormattedTimestamps(this.telemetryResults);
270
+ return withFormattedTimestamps(this.telemetry);
272
271
  }
273
272
 
274
273
  enableTelemetry(): void {
@@ -277,7 +276,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
277
276
  return;
278
277
  }
279
278
 
280
- this.telemetryResults = {
279
+ this.telemetry = {
281
280
  summary: {
282
281
  successCount: 0,
283
282
  failureCount: 0,
@@ -290,7 +289,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
290
289
  this._telemetryCbHandle = this.client.addPerformanceCallback(events => {
291
290
  events.forEach(e => {
292
291
  try {
293
- const {summary, events} = this.telemetryResults,
292
+ const {summary, events} = this.telemetry,
294
293
  {name, startTimeMs, durationMs, success, errorName, errorCode} = e,
295
294
  eTime = startTimeMs ?? Date.now();
296
295
 
@@ -339,10 +338,10 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
339
338
  // Wait for clientHealthService (this client likely initialized during earlier AUTHENTICATING.)
340
339
  this.addReaction({
341
340
  when: () => XH.appState === AppState.INITIALIZING_APP,
342
- run: () => XH.clientHealthService.addSource('msalClient', () => this.telemetryResults)
341
+ run: () => XH.clientHealthService.addSource('msalClient', () => this.telemetry)
343
342
  });
344
343
 
345
- this.logInfo('Telemetry enabled');
344
+ this.logDebug('Telemetry enabled');
346
345
  }
347
346
 
348
347
  disableTelemetry(): void {
@@ -449,3 +448,41 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
449
448
  this.logDebug('User Authenticated', account.username);
450
449
  }
451
450
  }
451
+
452
+ /**
453
+ * Telemetry produced by this client (if enabled) + included in {@link ClientHealthService}
454
+ * reporting. Leverages MSAL's opt-in support for emitting performance events.
455
+ * See https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/performance.md
456
+ */
457
+ interface MsalClientTelemetry {
458
+ /** Stats across all events */
459
+ summary: {
460
+ successCount: number;
461
+ failureCount: number;
462
+ maxDuration: number;
463
+ lastFailureTime: number;
464
+ };
465
+ /** Stats by event type */
466
+ events: Record<string, MsalEventTelemetry>;
467
+ }
468
+
469
+ /** Aggregated telemetry results for a single type of event. */
470
+ interface MsalEventTelemetry {
471
+ firstTime: number;
472
+ lastTime: number;
473
+ successCount: number;
474
+ failureCount: number;
475
+ /** Timing info (in ms) for event instances reported with duration. */
476
+ duration?: {
477
+ count: number;
478
+ total: number;
479
+ average: number;
480
+ max: number;
481
+ };
482
+ lastFailure?: {
483
+ time: number;
484
+ duration: number;
485
+ code: string;
486
+ name: string;
487
+ };
488
+ }
@@ -5,6 +5,7 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {HoistService, PageState, PlainObject, TrackOptions, XH} from '@xh/hoist/core';
8
+ import {WebSocketTelemetry} from '@xh/hoist/svc/WebSocketService';
8
9
  import {Timer} from '@xh/hoist/utils/async';
9
10
  import {MINUTES} from '@xh/hoist/utils/datetime';
10
11
  import {withFormattedTimestamps} from '@xh/hoist/format';
@@ -42,6 +43,7 @@ export class ClientHealthService extends HoistService {
42
43
  general: this.getGeneral(),
43
44
  memory: this.getMemory(),
44
45
  connection: this.getConnection(),
46
+ webSockets: XH.webSocketService.telemetry,
45
47
  ...this.getCustom()
46
48
  };
47
49
  }
@@ -87,8 +89,7 @@ export class ClientHealthService extends HoistService {
87
89
  startTime,
88
90
  durationMins: elapsedMins(startTime),
89
91
  idleMins: elapsedMins(XH.lastActivityMs),
90
- pageState: XH.pageState,
91
- webSocket: XH.webSocketService.channelKey
92
+ pageState: XH.pageState
92
93
  };
93
94
  }
94
95
 
@@ -154,7 +155,6 @@ export interface GeneralData {
154
155
  durationMins: number;
155
156
  idleMins: number;
156
157
  pageState: PageState;
157
- webSocket: string;
158
158
  }
159
159
 
160
160
  export interface ConnectionData {
@@ -175,4 +175,5 @@ export interface ClientHealthReport {
175
175
  general: GeneralData;
176
176
  connection: ConnectionData;
177
177
  memory: MemoryData;
178
+ webSockets: WebSocketTelemetry;
178
179
  }