@xh/hoist 73.0.0-SNAPSHOT.1744145928224 → 73.0.0-SNAPSHOT.1744206740883
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 +9 -1
- package/admin/AdminUtils.ts +18 -1
- package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +3 -3
- package/admin/tabs/cluster/instances/BaseInstanceModel.ts +1 -29
- package/admin/tabs/cluster/instances/services/DetailsPanel.ts +3 -1
- package/admin/tabs/cluster/objects/DetailModel.ts +5 -40
- package/admin/tabs/cluster/objects/DetailPanel.ts +3 -1
- package/appcontainer/AppContainerModel.ts +2 -0
- package/appcontainer/AppStateModel.ts +1 -2
- package/build/types/admin/AdminUtils.d.ts +4 -0
- package/build/types/admin/tabs/cluster/instances/BaseInstanceModel.d.ts +1 -3
- package/build/types/admin/tabs/cluster/objects/DetailModel.d.ts +1 -3
- package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +7 -0
- package/build/types/core/XH.d.ts +2 -1
- package/build/types/format/FormatMisc.d.ts +3 -2
- package/build/types/svc/ClientHealthService.d.ts +80 -0
- package/build/types/svc/TrackService.d.ts +0 -12
- package/build/types/svc/index.d.ts +1 -0
- package/build/types/utils/js/index.d.ts +0 -1
- package/cmp/viewmanager/ViewManagerModel.ts +10 -1
- package/core/XH.ts +3 -1
- package/desktop/cmp/viewmanager/ViewMenu.ts +11 -9
- package/format/FormatMisc.ts +6 -4
- package/package.json +1 -1
- package/security/msal/MsalClient.ts +2 -6
- package/svc/ClientHealthService.ts +219 -0
- package/svc/TrackService.ts +3 -67
- package/svc/index.ts +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/utils/js/index.ts +0 -1
- package/build/types/utils/js/BrowserUtils.d.ts +0 -41
- package/utils/js/BrowserUtils.ts +0 -103
|
@@ -320,11 +320,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
|
|
|
320
320
|
// Handle TrackService not yet initialized (common, this client likely initialized before.)
|
|
321
321
|
this.addReaction({
|
|
322
322
|
when: () => XH.appIsRunning,
|
|
323
|
-
run: () =>
|
|
324
|
-
XH.trackService.addClientHealthReportSource(
|
|
325
|
-
'msalClient',
|
|
326
|
-
() => this.telemetryResults
|
|
327
|
-
)
|
|
323
|
+
run: () => XH.clientHealthService.addSource('msalClient', () => this.telemetryResults)
|
|
328
324
|
});
|
|
329
325
|
|
|
330
326
|
this.logInfo('Telemetry enabled');
|
|
@@ -339,7 +335,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
|
|
|
339
335
|
this.client.removePerformanceCallback(this._telemetryCbHandle);
|
|
340
336
|
this._telemetryCbHandle = null;
|
|
341
337
|
|
|
342
|
-
XH.
|
|
338
|
+
XH.clientHealthService.removeSource('msalClient');
|
|
343
339
|
this.logInfo('Telemetry disabled', this.telemetryResults);
|
|
344
340
|
}
|
|
345
341
|
|
|
@@ -0,0 +1,219 @@
|
|
|
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, PlainObject, XH} from '@xh/hoist/core';
|
|
8
|
+
import {Timer} from '@xh/hoist/utils/async';
|
|
9
|
+
import {MINUTES} from '@xh/hoist/utils/datetime';
|
|
10
|
+
import {find, isPlainObject, pick, round} from 'lodash';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Service for gathering data about client health.
|
|
14
|
+
*
|
|
15
|
+
* Hoist sends this data once on application load, and can be configured to send
|
|
16
|
+
* it at regularly scheduled intervals. Configure via soft-config property
|
|
17
|
+
* 'xhActivityTracking.clientHealthReport'.
|
|
18
|
+
*/
|
|
19
|
+
export class ClientHealthService extends HoistService {
|
|
20
|
+
static instance: ClientHealthService;
|
|
21
|
+
|
|
22
|
+
private sources: Map<string, () => any> = new Map();
|
|
23
|
+
|
|
24
|
+
override async initAsync() {
|
|
25
|
+
const {clientHealthReport} = XH.trackService.conf;
|
|
26
|
+
Timer.create({
|
|
27
|
+
runFn: () => this.sendReport(),
|
|
28
|
+
interval: clientHealthReport.intervalMins * MINUTES,
|
|
29
|
+
delay: true
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Main Entry report. Return a default report of client health.
|
|
35
|
+
*/
|
|
36
|
+
getReport(): ClientHealthReport {
|
|
37
|
+
return {
|
|
38
|
+
session: this.getSession(),
|
|
39
|
+
...this.getCustom(),
|
|
40
|
+
memory: this.getMemory(),
|
|
41
|
+
connection: this.getConnection(),
|
|
42
|
+
window: this.getWindow(),
|
|
43
|
+
screen: this.getScreen()
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Register a new source for client health report data. No-op if background health report is
|
|
49
|
+
* not generally enabled via `xhActivityTrackingConfig.clientHealthReport.intervalMins`.
|
|
50
|
+
*
|
|
51
|
+
* @param key - key under which to report the data - can be used to remove this source later.
|
|
52
|
+
* @param callback - function returning serializable to include with each report.
|
|
53
|
+
*/
|
|
54
|
+
addSource(key: string, callback: () => any) {
|
|
55
|
+
this.sources.set(key, callback);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Unregister a previously-enabled source for client health report data. */
|
|
59
|
+
removeSource(key: string) {
|
|
60
|
+
this.sources.delete(key);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// -----------------------------------
|
|
64
|
+
// Generate individual report sections
|
|
65
|
+
//------------------------------------
|
|
66
|
+
getSession(): SessionData {
|
|
67
|
+
const {loadStarted} = XH.appContainerModel.appStateModel;
|
|
68
|
+
return {
|
|
69
|
+
startTime: loadStarted,
|
|
70
|
+
durationMins: round((Date.now() - loadStarted) / 60_000, 1)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getConnection(): ConnectionData {
|
|
75
|
+
const nav = window.navigator as any;
|
|
76
|
+
if (!nav.connection) return null;
|
|
77
|
+
return pick(nav.connection, ['downlink', 'effectiveType', 'rtt']);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getMemory(): MemoryData {
|
|
81
|
+
const perf = window.performance as any;
|
|
82
|
+
if (!perf?.memory) return null;
|
|
83
|
+
|
|
84
|
+
const ret: MemoryData = {modelCount: XH.getModels().length};
|
|
85
|
+
['jsHeapSizeLimit', 'totalJSHeapSize', 'usedJSHeapSize'].forEach(key => {
|
|
86
|
+
const raw = perf.memory[key];
|
|
87
|
+
if (raw) ret[key] = round(raw / 1024 / 1024); // convert to MB
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const {jsHeapSizeLimit: limit, usedJSHeapSize: used} = ret;
|
|
91
|
+
if (limit && used) {
|
|
92
|
+
ret.usedPctLimit = round((used / limit) * 100, 1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return ret;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getScreen(): ScreenData {
|
|
99
|
+
const screen = window.screen as any;
|
|
100
|
+
if (!screen) return null;
|
|
101
|
+
|
|
102
|
+
const ret: ScreenData = pick(screen, [
|
|
103
|
+
'availWidth',
|
|
104
|
+
'availHeight',
|
|
105
|
+
'width',
|
|
106
|
+
'height',
|
|
107
|
+
'colorDepth',
|
|
108
|
+
'pixelDepth',
|
|
109
|
+
'availLeft',
|
|
110
|
+
'availTop'
|
|
111
|
+
]);
|
|
112
|
+
if (screen.orientation) {
|
|
113
|
+
ret.orientation = pick(screen.orientation, ['angle', 'type']);
|
|
114
|
+
}
|
|
115
|
+
return ret;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getWindow(): WindowData {
|
|
119
|
+
return pick(window, [
|
|
120
|
+
'devicePixelRatio',
|
|
121
|
+
'screenX',
|
|
122
|
+
'screenY',
|
|
123
|
+
'innerWidth',
|
|
124
|
+
'innerHeight',
|
|
125
|
+
'outerWidth',
|
|
126
|
+
'outerHeight'
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getCustom(): PlainObject {
|
|
131
|
+
const ret = {};
|
|
132
|
+
this.sources.forEach((cb, k) => {
|
|
133
|
+
try {
|
|
134
|
+
ret[k] = cb();
|
|
135
|
+
} catch (e) {
|
|
136
|
+
ret[k] = `Error: ${e.message}`;
|
|
137
|
+
this.logWarn(`Error running client health report callback for [${k}]`, e);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return ret;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//------------------
|
|
144
|
+
// Implementation
|
|
145
|
+
//------------------
|
|
146
|
+
private sendReport() {
|
|
147
|
+
const {
|
|
148
|
+
intervalMins,
|
|
149
|
+
severity: defaultSeverity,
|
|
150
|
+
...rest
|
|
151
|
+
} = XH.trackService.conf.clientHealthReport ?? {};
|
|
152
|
+
|
|
153
|
+
const rpt = this.getReport();
|
|
154
|
+
let severity = defaultSeverity ?? 'INFO';
|
|
155
|
+
if (find(rpt, (v: any) => isPlainObject(v) && v.severity === 'WARN')) {
|
|
156
|
+
severity = 'WARN';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
XH.track({
|
|
160
|
+
category: 'App',
|
|
161
|
+
message: 'Submitted health report',
|
|
162
|
+
severity,
|
|
163
|
+
...rest,
|
|
164
|
+
data: rpt
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface SessionData {
|
|
170
|
+
startTime: number;
|
|
171
|
+
durationMins: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface ConnectionData {
|
|
175
|
+
downlink: number;
|
|
176
|
+
effectiveType: string;
|
|
177
|
+
rtt: number;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface MemoryData {
|
|
181
|
+
modelCount: number;
|
|
182
|
+
usedPctLimit?: number;
|
|
183
|
+
jsHeapSizeLimit?: number;
|
|
184
|
+
totalJSHeapSize?: number;
|
|
185
|
+
usedJSHeapSize?: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface WindowData {
|
|
189
|
+
devicePixelRatio: number;
|
|
190
|
+
screenX: number;
|
|
191
|
+
screenY: number;
|
|
192
|
+
innerWidth: number;
|
|
193
|
+
innerHeight: number;
|
|
194
|
+
outerWidth: number;
|
|
195
|
+
outerHeight: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface ScreenData {
|
|
199
|
+
availWidth: number;
|
|
200
|
+
availHeight: number;
|
|
201
|
+
width: number;
|
|
202
|
+
height: number;
|
|
203
|
+
colorDepth: number;
|
|
204
|
+
pixelDepth: number;
|
|
205
|
+
availLeft: number;
|
|
206
|
+
availTop: number;
|
|
207
|
+
orientation?: {
|
|
208
|
+
angle: number;
|
|
209
|
+
type: string;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface ClientHealthReport {
|
|
214
|
+
session: SessionData;
|
|
215
|
+
connection: ConnectionData;
|
|
216
|
+
memory: MemoryData;
|
|
217
|
+
window: WindowData;
|
|
218
|
+
screen: ScreenData;
|
|
219
|
+
}
|
package/svc/TrackService.ts
CHANGED
|
@@ -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 {
|
|
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,
|
|
12
|
-
import {isEmpty, isNil, isString
|
|
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';
|