@xh/hoist 72.4.0 → 72.5.1

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 (34) hide show
  1. package/CHANGELOG.md +21 -8
  2. package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +22 -14
  3. package/admin/tabs/cluster/instances/logs/LogDisplayModel.ts +10 -14
  4. package/admin/tabs/cluster/instances/websocket/WebSocketColumns.ts +24 -2
  5. package/admin/tabs/cluster/instances/websocket/WebSocketModel.ts +76 -25
  6. package/admin/tabs/cluster/instances/websocket/WebSocketPanel.ts +2 -2
  7. package/appcontainer/AppStateModel.ts +3 -3
  8. package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +1 -1
  9. package/build/types/admin/tabs/cluster/instances/websocket/WebSocketColumns.d.ts +4 -1
  10. package/build/types/admin/tabs/cluster/instances/websocket/WebSocketModel.d.ts +5 -2
  11. package/build/types/core/XH.d.ts +4 -4
  12. package/build/types/core/types/Interfaces.d.ts +2 -2
  13. package/build/types/security/Types.d.ts +0 -25
  14. package/build/types/security/msal/MsalClient.d.ts +40 -4
  15. package/build/types/svc/ClientHealthService.d.ts +19 -13
  16. package/build/types/svc/TrackService.d.ts +5 -1
  17. package/build/types/svc/WebSocketService.d.ts +39 -15
  18. package/core/XH.ts +6 -6
  19. package/core/types/Interfaces.ts +2 -2
  20. package/data/filter/BaseFilterFieldSpec.ts +6 -2
  21. package/desktop/appcontainer/AboutDialog.ts +14 -0
  22. package/desktop/cmp/button/AppMenuButton.ts +1 -1
  23. package/desktop/cmp/contextmenu/ContextMenu.ts +1 -1
  24. package/kit/onsen/theme.scss +5 -0
  25. package/mobile/appcontainer/AboutDialog.scss +1 -1
  26. package/mobile/appcontainer/AboutDialog.ts +24 -1
  27. package/mobile/cmp/menu/impl/Menu.ts +2 -2
  28. package/package.json +1 -1
  29. package/security/Types.ts +0 -27
  30. package/security/msal/MsalClient.ts +66 -14
  31. package/svc/ClientHealthService.ts +35 -21
  32. package/svc/TrackService.ts +8 -4
  33. package/svc/WebSocketService.ts +80 -33
  34. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,14 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## v72.5.1 - 2025-04-15
4
+
5
+ ### 🐞 Bug Fixes
6
+
7
+ * Allow the display of very long log lines in Admin log viewer.
8
+
9
+ ## v72.5.0 - 2025-04-14
10
+
11
+ ### 🎁 New Features
12
+
13
+ * Added option from the Admin Console > Websockets tab to request a client health report from any
14
+ connected clients.
15
+ * Enabled telemetry reporting from `WebSocketService`.
16
+ * Updated `MenuItem.actionFn()` to receive the click event as an additional argument.
17
+ * Support for reporting App Build, Tab Id, and Load Id in websocket admin page.
18
+
3
19
  ## v72.4.0 - 2025-04-09
4
20
 
5
21
  ### 🎁 New Features
6
- * New methods for formatting timestamps within nested JSON objects. See `withFormattedTimestamps`
22
+
23
+ * Added new methods for formatting timestamps within JSON objects. See `withFormattedTimestamps`
7
24
  and `timestampReplacer` in the `@xh/hoist/format` package.
8
- * `ViewManagerConfig` takes new optional key `viewMenuItemFn` to allow ViewManager implementations
9
- to customize the menu items for views in the view manager menu.
25
+ * Added new `ViewManagerConfig.viewMenuItemFn` option to support custom rendering of pinned views in
26
+ the drop-down menu.
10
27
 
11
28
  ### ⚙️ Technical
29
+
12
30
  * Added dedicated `ClientHealthService` for managing client health report. Additional enhancements
13
31
  to health report to include information about web sockets, idle time, and page state.
14
32
 
@@ -28,11 +46,6 @@
28
46
 
29
47
  * Improved fetch request tracking to include time spent loading headers as specified by application.
30
48
 
31
- ### ⚙️ Technical
32
-
33
- * Update shape of returned `BrowserUtils.getClientDeviceInfo()` to nest several properties under new
34
- top-level `window` key and report JS heap size / usage values under the `memory` block in MB.
35
-
36
49
  ### 📚 Libraries
37
50
 
38
51
  * @azure/msal-browser `3.28 → 4.8.0`
@@ -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
@@ -138,18 +138,23 @@ export class LogDisplayModel extends HoistModel {
138
138
  sizingMode: 'tiny',
139
139
  emptyText: 'No log entries found...',
140
140
  sortBy: 'rowNum|asc',
141
+ autosizeOptions: {mode: 'disabled'},
141
142
  store: {
142
143
  idSpec: 'rowNum'
143
144
  },
144
145
  columns: [
145
146
  {
146
147
  field: {name: 'rowNum', type: 'number'},
147
- width: 4,
148
+ width: 60,
149
+ pinned: true,
148
150
  cellClass: 'xh-log-display__row-number'
149
151
  },
150
152
  {
151
153
  field: 'rowContent',
152
- width: 1200,
154
+ // Hard-code to a very wide value - allows us to avoid autosize overhead while
155
+ // ensuring that all but crazy-long lines are readable. We trust Admins can
156
+ // ignore excess horizontal scrolling for this component.
157
+ width: 5000,
153
158
  autosizable: false,
154
159
  cellClass: 'xh-log-display__row-content'
155
160
  }
@@ -179,20 +184,11 @@ export class LogDisplayModel extends HoistModel {
179
184
  }
180
185
 
181
186
  private updateGridData(data) {
182
- const {tailActive, gridModel} = this;
183
- let maxRowLength = 200;
184
- const gridData = data.map(row => {
185
- if (row[1].length > maxRowLength) {
186
- maxRowLength = row[1].length;
187
- }
188
- return {
187
+ const {tailActive, gridModel} = this,
188
+ gridData = data.map(row => ({
189
189
  rowNum: row[0],
190
190
  rowContent: row[1]
191
- };
192
- });
193
-
194
- // Estimate the length of the row in pixels based on (character count) * (font size)
195
- gridModel.setColumnState([{colId: 'rowContent', width: maxRowLength * 6}]);
191
+ }));
196
192
 
197
193
  gridModel.loadData(gridData);
198
194
 
@@ -73,11 +73,33 @@ export const lastReceivedTime: ColumnSpec = {
73
73
  width: 140
74
74
  };
75
75
 
76
- export const clientAppVersion: ColumnSpec = {
76
+ export const appVersion: ColumnSpec = {
77
77
  field: {
78
- name: 'clientAppVersion',
78
+ name: 'appVersion',
79
79
  type: 'string',
80
80
  displayName: 'Client Version'
81
81
  },
82
82
  width: 120
83
83
  };
84
+
85
+ export const appBuild: ColumnSpec = {
86
+ field: {
87
+ name: 'appBuild',
88
+ type: 'string'
89
+ },
90
+ width: 120
91
+ };
92
+ export const loadId: ColumnSpec = {
93
+ field: {
94
+ name: 'loadId',
95
+ type: 'string'
96
+ },
97
+ width: 120
98
+ };
99
+ export const tabId: ColumnSpec = {
100
+ field: {
101
+ name: 'tabId',
102
+ type: 'string'
103
+ },
104
+ width: 120
105
+ };
@@ -5,20 +5,21 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {exportFilenameWithDate} from '@xh/hoist/admin/AdminUtils';
8
+ import {AppModel} from '@xh/hoist/admin/AppModel';
8
9
  import * as Col from '@xh/hoist/admin/columns';
9
10
  import {BaseInstanceModel} from '@xh/hoist/admin/tabs/cluster/instances/BaseInstanceModel';
10
11
  import {GridModel} from '@xh/hoist/cmp/grid';
11
12
  import {div, p} from '@xh/hoist/cmp/layout';
12
13
  import {LoadSpec, managed, XH} from '@xh/hoist/core';
14
+ import {RecordActionSpec, StoreRecord} from '@xh/hoist/data';
13
15
  import {textInput} from '@xh/hoist/desktop/cmp/input';
14
16
  import {Icon} from '@xh/hoist/icon';
15
17
  import {makeObservable, observable, runInAction} from '@xh/hoist/mobx';
16
18
  import {Timer} from '@xh/hoist/utils/async';
17
19
  import {SECONDS} from '@xh/hoist/utils/datetime';
20
+ import {pluralize} from '@xh/hoist/utils/js';
18
21
  import {isEmpty} from 'lodash';
19
22
  import * as WSCol from './WebSocketColumns';
20
- import {RecordActionSpec} from '@xh/hoist/data';
21
- import {AppModel} from '@xh/hoist/admin/AppModel';
22
23
 
23
24
  export class WebSocketModel extends BaseInstanceModel {
24
25
  @observable
@@ -34,11 +35,19 @@ export class WebSocketModel extends BaseInstanceModel {
34
35
  text: 'Force suspend',
35
36
  icon: Icon.stopCircle(),
36
37
  intent: 'danger',
37
- actionFn: () => this.forceSuspendAsync(),
38
+ actionFn: ({selectedRecords}) => this.forceSuspendAsync(selectedRecords),
38
39
  displayFn: () => ({hidden: AppModel.readonly}),
39
40
  recordsRequired: true
40
41
  };
41
42
 
43
+ reqHealthReportAction: RecordActionSpec = {
44
+ text: 'Request Health Report',
45
+ icon: Icon.health(),
46
+ actionFn: ({selectedRecords}) => this.requestHealthReportAsync(selectedRecords),
47
+ recordsRequired: true,
48
+ hidden: !XH.trackService.enabled
49
+ };
50
+
42
51
  constructor() {
43
52
  super();
44
53
  makeObservable(this);
@@ -48,7 +57,12 @@ export class WebSocketModel extends BaseInstanceModel {
48
57
  enableExport: true,
49
58
  exportOptions: {filename: exportFilenameWithDate('ws-connections')},
50
59
  selModel: 'multiple',
51
- contextMenu: [this.forceSuspendAction, '-', ...GridModel.defaultContextMenu],
60
+ contextMenu: [
61
+ this.forceSuspendAction,
62
+ this.reqHealthReportAction,
63
+ '-',
64
+ ...GridModel.defaultContextMenu
65
+ ],
52
66
  store: {
53
67
  idSpec: 'key',
54
68
  processRawData: row => {
@@ -78,7 +92,10 @@ export class WebSocketModel extends BaseInstanceModel {
78
92
  WSCol.lastSentTime,
79
93
  WSCol.receivedMessageCount,
80
94
  WSCol.lastReceivedTime,
81
- WSCol.clientAppVersion
95
+ WSCol.appVersion,
96
+ WSCol.appBuild,
97
+ WSCol.loadId,
98
+ WSCol.tabId
82
99
  ]
83
100
  });
84
101
 
@@ -107,9 +124,8 @@ export class WebSocketModel extends BaseInstanceModel {
107
124
  }
108
125
  }
109
126
 
110
- async forceSuspendAsync() {
111
- const {selectedRecords} = this.gridModel;
112
- if (isEmpty(selectedRecords)) return;
127
+ async forceSuspendAsync(toRecs: StoreRecord[]) {
128
+ if (isEmpty(toRecs)) return;
113
129
 
114
130
  const message = await XH.prompt<string>({
115
131
  title: 'Please confirm...',
@@ -123,7 +139,7 @@ export class WebSocketModel extends BaseInstanceModel {
123
139
  },
124
140
  message: div(
125
141
  p(
126
- `This action will force ${selectedRecords.length} connected client(s) into suspended mode, halting all background refreshes and other activity, masking the UI, and requiring users to reload the app to continue.`
142
+ `This action will force ${toRecs.length} connected client(s) into suspended mode, halting all background refreshes and other activity, masking the UI, and requiring users to reload the app to continue.`
127
143
  ),
128
144
  p('Enter an optional message below to display within the suspended app.')
129
145
  ),
@@ -134,23 +150,58 @@ export class WebSocketModel extends BaseInstanceModel {
134
150
  });
135
151
 
136
152
  if (message !== false) {
137
- const tasks = selectedRecords.map(rec =>
138
- XH.fetchJson({
139
- url: 'webSocketAdmin/pushToChannel',
140
- params: {
141
- channelKey: rec.data.key,
142
- topic: XH.webSocketService.FORCE_APP_SUSPEND_TOPIC,
143
- instance: this.instanceName,
144
- message
145
- }
146
- })
147
- );
148
-
149
- await Promise.allSettled(tasks).track({
150
- category: 'Audit',
151
- message: 'Suspended clients via WebSocket',
152
- data: {users: selectedRecords.map(it => it.data.user).sort()}
153
+ await this.bulkPush({
154
+ toRecs,
155
+ topic: XH.webSocketService.FORCE_APP_SUSPEND_TOPIC,
156
+ message,
157
+ trackMessage: 'Suspended clients via WebSocket'
153
158
  });
154
159
  }
155
160
  }
161
+
162
+ async requestHealthReportAsync(toRecs: StoreRecord[]) {
163
+ await this.bulkPush({
164
+ toRecs,
165
+ topic: XH.webSocketService.REQ_CLIENT_HEALTH_RPT_TOPIC
166
+ });
167
+ XH.successToast(
168
+ `Client health report requested for ${pluralize('client', toRecs.length, true)} - available in User Activity shortly...`
169
+ );
170
+ }
171
+
172
+ //------------------
173
+ // Implementation
174
+ //------------------
175
+ private async bulkPush({
176
+ toRecs,
177
+ topic,
178
+ message,
179
+ trackMessage
180
+ }: {
181
+ toRecs?: StoreRecord[];
182
+ topic: string;
183
+ message?: string;
184
+ trackMessage?: string;
185
+ }) {
186
+ if (isEmpty(toRecs)) return;
187
+
188
+ const tasks = toRecs.map(rec =>
189
+ XH.fetchJson({
190
+ url: 'webSocketAdmin/pushToChannel',
191
+ params: {
192
+ channelKey: rec.data.key,
193
+ instance: this.instanceName,
194
+ topic,
195
+ message
196
+ }
197
+ })
198
+ );
199
+
200
+ await Promise.allSettled(tasks).track({
201
+ category: 'Audit',
202
+ message: trackMessage,
203
+ data: {users: toRecs.map(it => it.data.user).sort()},
204
+ omit: !trackMessage
205
+ });
206
+ }
156
207
  }
@@ -16,7 +16,7 @@ import {panel} from '@xh/hoist/desktop/cmp/panel';
16
16
  import {recordActionBar} from '@xh/hoist/desktop/cmp/record';
17
17
  import {toolbarSep} from '@xh/hoist/desktop/cmp/toolbar';
18
18
 
19
- export const webSocketPanel = hoistCmp.factory({
19
+ export const webSocketPanel = hoistCmp.factory<WebSocketModel>({
20
20
  model: creates(WebSocketModel),
21
21
 
22
22
  render({model}) {
@@ -26,7 +26,7 @@ export const webSocketPanel = hoistCmp.factory({
26
26
  bbar: [
27
27
  recordActionBar({
28
28
  selModel: model.gridModel.selModel,
29
- actions: [model.forceSuspendAction]
29
+ actions: [model.forceSuspendAction, model.reqHealthReportAction]
30
30
  }),
31
31
  filler(),
32
32
  relativeTimestamp({bind: 'lastRefresh', options: {prefix: 'Refreshed'}}),
@@ -92,14 +92,14 @@ export class AppStateModel extends HoistModel {
92
92
  timestamp: loadStarted,
93
93
  elapsed: Date.now() - loadStarted - (timings.LOGIN_REQUIRED ?? 0),
94
94
  data: {
95
- clientId: XH.clientId,
96
- sessionId: XH.sessionId,
95
+ loadId: XH.loadId,
96
+ tabId: XH.tabId,
97
97
  timings: mapKeys(timings, (v, k) => camelCase(k)),
98
98
  clientHealth: XH.clientHealthService.getReport(),
99
99
  window: this.getWindowData(),
100
100
  screen: this.getScreenData()
101
101
  },
102
- logData: ['clientId', 'sessionId'],
102
+ logData: ['loadId', 'tabId'],
103
103
  omit: !XH.appSpec.trackAppLoad
104
104
  })
105
105
  });
@@ -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';
@@ -6,4 +6,7 @@ export declare const sentMessageCount: ColumnSpec;
6
6
  export declare const lastSentTime: ColumnSpec;
7
7
  export declare const receivedMessageCount: ColumnSpec;
8
8
  export declare const lastReceivedTime: ColumnSpec;
9
- export declare const clientAppVersion: ColumnSpec;
9
+ export declare const appVersion: ColumnSpec;
10
+ export declare const appBuild: ColumnSpec;
11
+ export declare const loadId: ColumnSpec;
12
+ export declare const tabId: ColumnSpec;
@@ -1,13 +1,16 @@
1
1
  import { BaseInstanceModel } from '@xh/hoist/admin/tabs/cluster/instances/BaseInstanceModel';
2
2
  import { GridModel } from '@xh/hoist/cmp/grid';
3
3
  import { LoadSpec } from '@xh/hoist/core';
4
- import { RecordActionSpec } from '@xh/hoist/data';
4
+ import { RecordActionSpec, StoreRecord } from '@xh/hoist/data';
5
5
  export declare class WebSocketModel extends BaseInstanceModel {
6
6
  lastRefresh: number;
7
7
  gridModel: GridModel;
8
8
  private _timer;
9
9
  forceSuspendAction: RecordActionSpec;
10
+ reqHealthReportAction: RecordActionSpec;
10
11
  constructor();
11
12
  doLoadAsync(loadSpec: LoadSpec): Promise<void>;
12
- forceSuspendAsync(): Promise<void>;
13
+ forceSuspendAsync(toRecs: StoreRecord[]): Promise<void>;
14
+ requestHealthReportAsync(toRecs: StoreRecord[]): Promise<void>;
15
+ private bulkPush;
13
16
  }
@@ -22,12 +22,12 @@ export declare const MIN_HOIST_CORE_VERSION = "28.0";
22
22
  */
23
23
  export declare class XHApi {
24
24
  /** Unique id for this loaded instance of the app. Unique for every refresh of document. */
25
- clientId: string;
25
+ loadId: string;
26
26
  /**
27
27
  * Unique id for this browser tab/window on this domain.
28
28
  * Corresponds to the scope of the built-in sessionStorage object.
29
29
  */
30
- sessionId: string;
30
+ tabId: string;
31
31
  /** Core implementation model hosting all application state. */
32
32
  appContainerModel: AppContainerModel;
33
33
  /** Provider of centralized exception handling for the app. */
@@ -412,8 +412,8 @@ export declare class XHApi {
412
412
  */
413
413
  genId(): string;
414
414
  private get acm();
415
- private genClientId;
416
- private genSessionId;
415
+ private genLoadId;
416
+ private genTabId;
417
417
  }
418
418
  /** The app-wide singleton instance. */
419
419
  export declare const XH: XHApi;
@@ -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,28 +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 by event type - */
18
- events: Record<string, TelemetryEventResults>;
19
- }
20
- /** Aggregated telemetry results for a single type of event. */
21
- export interface TelemetryEventResults {
22
- firstTime: number;
23
- lastTime: number;
24
- successCount: number;
25
- failureCount: number;
26
- /** Timing info (in ms) for event instances reported with duration. */
27
- duration: {
28
- count: number;
29
- total: number;
30
- average: number;
31
- worst: number;
32
- };
33
- lastFailure?: {
34
- time: number;
35
- duration: number;
36
- code: string;
37
- name: string;
38
- };
39
- }
@@ -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,43 +1,48 @@
1
1
  import { HoistService, PageState, PlainObject } from '@xh/hoist/core';
2
+ import { WebSocketTelemetry } from '@xh/hoist/svc/WebSocketService';
2
3
  /**
3
- * Service for gathering data about client health.
4
+ * Service for gathering data about the current state and health of the client app, for submission
5
+ * to the server or review on the console during interactive troubleshooting.
4
6
  *
5
- * Hoist sends this data once on application load, and can be configured to send
6
- * it at regularly scheduled intervals. Configure via soft-config property
7
- * 'xhActivityTracking.clientHealthReport'.
7
+ * Hoist sends this data once on application load and can be configured to send at regular intervals
8
+ * throughout a user's session via the `xhActivityTracking.clientHealthReport` app config. Reports
9
+ * are submitted via activity tracking for review within the Admin Console.
8
10
  */
9
11
  export declare class ClientHealthService extends HoistService {
10
12
  static instance: ClientHealthService;
11
13
  private sources;
12
14
  initAsync(): Promise<void>;
13
- /**
14
- * Main entry point. Return a default report of client health.
15
- */
15
+ get enabled(): boolean;
16
+ /** @returns a customizable report with metrics capturing client app/session state. */
16
17
  getReport(): ClientHealthReport;
17
- /** Get report, suitable for viewing in console. **/
18
+ /** @returns a report, formatted for easier viewing in console. **/
18
19
  getFormattedReport(): PlainObject;
19
20
  /**
20
- * Register a new source for client health report data. No-op if background health report is
21
- * not generally enabled via `xhActivityTrackingConfig.clientHealthReport.intervalMins`.
22
- *
21
+ * Register a new source for app-specific data to be sent with each report.
23
22
  * @param key - key under which to report the data - can be used to remove this source later.
24
23
  * @param callback - function returning serializable to include with each report.
25
24
  */
26
25
  addSource(key: string, callback: () => any): void;
27
26
  /** Unregister a previously-enabled source for client health report data. */
28
27
  removeSource(key: string): void;
28
+ /**
29
+ * Generate and submit a report to the server, via TrackService.
30
+ *
31
+ * For ad-hoc troubleshooting. Apps may also configure this service to
32
+ * submit on regular intervals.
33
+ */
34
+ sendReportAsync(): Promise<void>;
29
35
  getGeneral(): GeneralData;
30
36
  getConnection(): ConnectionData;
31
37
  getMemory(): MemoryData;
32
38
  getCustom(): PlainObject;
33
- private sendReport;
39
+ private sendReportInternal;
34
40
  }
35
41
  export interface GeneralData {
36
42
  startTime: number;
37
43
  durationMins: number;
38
44
  idleMins: number;
39
45
  pageState: PageState;
40
- webSocket: string;
41
46
  }
42
47
  export interface ConnectionData {
43
48
  downlink: number;
@@ -55,4 +60,5 @@ export interface ClientHealthReport {
55
60
  general: GeneralData;
56
61
  connection: ConnectionData;
57
62
  memory: MemoryData;
63
+ webSockets: WebSocketTelemetry;
58
64
  }