@xh/hoist 72.4.0 → 72.5.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.
- package/CHANGELOG.md +15 -8
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +22 -14
- package/admin/tabs/cluster/instances/websocket/WebSocketColumns.ts +24 -2
- package/admin/tabs/cluster/instances/websocket/WebSocketModel.ts +76 -25
- package/admin/tabs/cluster/instances/websocket/WebSocketPanel.ts +2 -2
- package/appcontainer/AppStateModel.ts +3 -3
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +1 -1
- package/build/types/admin/tabs/cluster/instances/websocket/WebSocketColumns.d.ts +4 -1
- package/build/types/admin/tabs/cluster/instances/websocket/WebSocketModel.d.ts +5 -2
- package/build/types/core/XH.d.ts +4 -4
- package/build/types/core/types/Interfaces.d.ts +2 -2
- package/build/types/security/Types.d.ts +0 -25
- package/build/types/security/msal/MsalClient.d.ts +40 -4
- package/build/types/svc/ClientHealthService.d.ts +19 -13
- package/build/types/svc/TrackService.d.ts +5 -1
- package/build/types/svc/WebSocketService.d.ts +38 -15
- package/core/XH.ts +6 -6
- package/core/types/Interfaces.ts +2 -2
- package/data/filter/BaseFilterFieldSpec.ts +6 -2
- package/desktop/appcontainer/AboutDialog.ts +14 -0
- package/desktop/cmp/button/AppMenuButton.ts +1 -1
- package/desktop/cmp/contextmenu/ContextMenu.ts +1 -1
- package/kit/onsen/theme.scss +5 -0
- package/mobile/appcontainer/AboutDialog.scss +1 -1
- package/mobile/appcontainer/AboutDialog.ts +24 -1
- package/mobile/cmp/menu/impl/Menu.ts +2 -2
- package/package.json +1 -1
- package/security/Types.ts +0 -27
- package/security/msal/MsalClient.ts +66 -14
- package/svc/ClientHealthService.ts +35 -21
- package/svc/TrackService.ts +8 -4
- package/svc/WebSocketService.ts +74 -33
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v72.5.0 - 2025-04-14
|
|
4
|
+
|
|
5
|
+
### 🎁 New Features
|
|
6
|
+
|
|
7
|
+
* Added option from the Admin Console > Websockets tab to request a client health report from any
|
|
8
|
+
connected clients.
|
|
9
|
+
* Enabled telemetry reporting from `WebSocketService`.
|
|
10
|
+
* Updated `MenuItem.actionFn()` to receive the click event as an additional argument.
|
|
11
|
+
* Support for reporting App Build, Tab Id, and Load Id in websocket admin page.
|
|
12
|
+
|
|
3
13
|
## v72.4.0 - 2025-04-09
|
|
4
14
|
|
|
5
15
|
### 🎁 New Features
|
|
6
|
-
|
|
16
|
+
|
|
17
|
+
* Added new methods for formatting timestamps within JSON objects. See `withFormattedTimestamps`
|
|
7
18
|
and `timestampReplacer` in the `@xh/hoist/format` package.
|
|
8
|
-
* `ViewManagerConfig`
|
|
9
|
-
|
|
19
|
+
* Added new `ViewManagerConfig.viewMenuItemFn` option to support custom rendering of pinned views in
|
|
20
|
+
the drop-down menu.
|
|
10
21
|
|
|
11
22
|
### ⚙️ Technical
|
|
23
|
+
|
|
12
24
|
* Added dedicated `ClientHealthService` for managing client health report. Additional enhancements
|
|
13
25
|
to health report to include information about web sockets, idle time, and page state.
|
|
14
26
|
|
|
@@ -28,11 +40,6 @@
|
|
|
28
40
|
|
|
29
41
|
* Improved fetch request tracking to include time spent loading headers as specified by application.
|
|
30
42
|
|
|
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
43
|
### 📚 Libraries
|
|
37
44
|
|
|
38
45
|
* @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
|
|
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
|
|
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'
|
|
114
|
+
{field: 'category'},
|
|
116
115
|
{field: 'correlationId'},
|
|
117
|
-
{field: 'username', displayName: 'User'
|
|
118
|
-
{field: 'device'
|
|
119
|
-
{field: 'browser'
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
|
@@ -73,11 +73,33 @@ export const lastReceivedTime: ColumnSpec = {
|
|
|
73
73
|
width: 140
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
export const
|
|
76
|
+
export const appVersion: ColumnSpec = {
|
|
77
77
|
field: {
|
|
78
|
-
name: '
|
|
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: [
|
|
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.
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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: ['
|
|
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
|
|
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
|
}
|
package/build/types/core/XH.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
416
|
-
private
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
15
|
-
*/
|
|
15
|
+
get enabled(): boolean;
|
|
16
|
+
/** @returns a customizable report with metrics capturing client app/session state. */
|
|
16
17
|
getReport(): ClientHealthReport;
|
|
17
|
-
/**
|
|
18
|
+
/** @returns a report, formatted for easier viewing in console. **/
|
|
18
19
|
getFormattedReport(): PlainObject;
|
|
19
20
|
/**
|
|
20
|
-
* Register a new source for
|
|
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
|
|
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
|
}
|
|
@@ -13,7 +13,11 @@ export declare class TrackService extends HoistService {
|
|
|
13
13
|
get enabled(): boolean;
|
|
14
14
|
/** Track User Activity. */
|
|
15
15
|
track(options: TrackOptions | string): void;
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Flush the queue of pending activity tracking messages to the server.
|
|
18
|
+
* @internal - apps should generally allow this service to manage w/its internal debounce.
|
|
19
|
+
*/
|
|
20
|
+
pushPendingAsync(): Promise<void>;
|
|
17
21
|
private pushPendingBuffered;
|
|
18
22
|
private toServerJson;
|
|
19
23
|
private logMessage;
|