@xh/hoist 73.0.0-SNAPSHOT.1744145928224 → 73.0.0-SNAPSHOT.1744147015222
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 +4 -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/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/core/XH.ts +3 -1
- 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
package/CHANGELOG.md
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## v73.0.0-SNAPSHOT - unreleased
|
|
4
4
|
|
|
5
|
+
### ⚙️ Technical
|
|
6
|
+
* Added enhanced `ClientHealthService` for managing client health report.
|
|
7
|
+
|
|
5
8
|
## v72.3.0 - 2025-04-08
|
|
6
9
|
|
|
7
10
|
### 🎁 New Features
|
|
8
11
|
|
|
9
12
|
* Added support for posting a "Client Health Report" track message on a configurable interval. This
|
|
10
13
|
message will include basic client information, and can be extended to include any other desired
|
|
11
|
-
data via `XH.
|
|
14
|
+
data via `XH.clientHealthService.addSource()`. Enable by updating your app's
|
|
12
15
|
`xhActivityTrackingConfig` to include `clientHealthReport: {intervalMins: XXXX}`.
|
|
13
16
|
* Enabled opt-in support for telemetry in `MsalClient`, leveraging hooks built-in to MSAL to collect
|
|
14
17
|
timing and success/failure count for all events emitted by the library.
|
package/admin/AdminUtils.ts
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {XH} from '@xh/hoist/core';
|
|
8
|
-
import {LocalDate} from '@xh/hoist/utils/datetime';
|
|
8
|
+
import {DAYS, LocalDate} from '@xh/hoist/utils/datetime';
|
|
9
|
+
import {isNumber} from 'lodash';
|
|
10
|
+
import {fmtDateTimeSec} from '@xh/hoist/format';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Generate a standardized filename for an Admin module grid export, without datestamp.
|
|
@@ -21,3 +23,18 @@ export function exportFilename(moduleName: string): string {
|
|
|
21
23
|
export function exportFilenameWithDate(moduleName: string): () => string {
|
|
22
24
|
return () => `${XH.appCode}-${moduleName}-${LocalDate.today()}`;
|
|
23
25
|
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Replacer for JSON.stringify to replace timestamp properties with formatted strings.
|
|
29
|
+
*/
|
|
30
|
+
export function timestampReplacer(k: string, v: any) {
|
|
31
|
+
if (
|
|
32
|
+
(k.endsWith('Time') || k.endsWith('Date') || k.endsWith('Timestamp') || k == 'timestamp') &&
|
|
33
|
+
isNumber(v) &&
|
|
34
|
+
v > Date.now() - 25 * 365 * DAYS // heuristic to avoid catching smaller ms ranges
|
|
35
|
+
) {
|
|
36
|
+
return fmtDateTimeSec(v, {fmt: 'MMM DD HH:mm:ss.SSS'});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {exportFilename} from '@xh/hoist/admin/AdminUtils';
|
|
7
|
+
import {exportFilename, timestampReplacer} from '@xh/hoist/admin/AdminUtils';
|
|
8
8
|
import * as Col from '@xh/hoist/admin/columns';
|
|
9
9
|
import {FormModel} from '@xh/hoist/cmp/form';
|
|
10
10
|
import {GridModel} from '@xh/hoist/cmp/grid';
|
|
11
11
|
import {HoistModel, lookup, managed} from '@xh/hoist/core';
|
|
12
|
-
import {fmtJson} from '@xh/hoist/format';
|
|
13
12
|
import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
|
|
14
13
|
import {ActivityTrackingModel} from '../ActivityTrackingModel';
|
|
14
|
+
import {fmtJson} from '@xh/hoist/format';
|
|
15
15
|
|
|
16
16
|
export class ActivityDetailModel extends HoistModel {
|
|
17
17
|
@lookup(ActivityTrackingModel) activityTrackingModel: ActivityTrackingModel;
|
|
@@ -118,7 +118,7 @@ export class ActivityDetailModel extends HoistModel {
|
|
|
118
118
|
let formattedTrackData = trackData;
|
|
119
119
|
if (formattedTrackData) {
|
|
120
120
|
try {
|
|
121
|
-
formattedTrackData = fmtJson(trackData);
|
|
121
|
+
formattedTrackData = fmtJson(trackData, {replacer: timestampReplacer});
|
|
122
122
|
} catch (ignored) {}
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -5,10 +5,7 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {InstancesTabModel} from '@xh/hoist/admin/tabs/cluster/instances/InstancesTabModel';
|
|
8
|
-
import {HoistModel, LoadSpec, lookup,
|
|
9
|
-
import {fmtDateTimeSec, fmtJson} from '@xh/hoist/format';
|
|
10
|
-
import {DAYS} from '@xh/hoist/utils/datetime';
|
|
11
|
-
import {cloneDeep, forOwn, isArray, isNumber, isPlainObject} from 'lodash';
|
|
8
|
+
import {HoistModel, LoadSpec, lookup, XH} from '@xh/hoist/core';
|
|
12
9
|
import {createRef} from 'react';
|
|
13
10
|
import {isDisplayed} from '@xh/hoist/utils/js';
|
|
14
11
|
|
|
@@ -21,12 +18,6 @@ export class BaseInstanceModel extends HoistModel {
|
|
|
21
18
|
return this.parent.instanceName;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
fmtStats(stats: PlainObject): string {
|
|
25
|
-
stats = cloneDeep(stats);
|
|
26
|
-
this.processTimestamps(stats);
|
|
27
|
-
return fmtJson(JSON.stringify(stats));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
21
|
handleLoadException(e: unknown, loadSpec: LoadSpec) {
|
|
31
22
|
const instanceNotFound = this.isInstanceNotFound(e),
|
|
32
23
|
connDown = this.parent.lastLoadException,
|
|
@@ -49,23 +40,4 @@ export class BaseInstanceModel extends HoistModel {
|
|
|
49
40
|
private isInstanceNotFound(e: unknown): boolean {
|
|
50
41
|
return e['name'] == 'InstanceNotFoundException';
|
|
51
42
|
}
|
|
52
|
-
|
|
53
|
-
private processTimestamps(stats: PlainObject) {
|
|
54
|
-
forOwn(stats, (v, k) => {
|
|
55
|
-
// Convert numbers that look like recent timestamps to date values.
|
|
56
|
-
if (
|
|
57
|
-
(k.endsWith('Time') ||
|
|
58
|
-
k.endsWith('Date') ||
|
|
59
|
-
k.endsWith('Timestamp') ||
|
|
60
|
-
k == 'timestamp') &&
|
|
61
|
-
isNumber(v) &&
|
|
62
|
-
v > Date.now() - 365 * DAYS
|
|
63
|
-
) {
|
|
64
|
-
stats[k] = v ? fmtDateTimeSec(v, {fmt: 'MMM DD HH:mm:ss.SSS'}) : null;
|
|
65
|
-
}
|
|
66
|
-
if (isPlainObject(v) || isArray(v)) {
|
|
67
|
-
this.processTimestamps(v);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
43
|
}
|
|
@@ -12,6 +12,8 @@ import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
|
12
12
|
import {jsonInput} from '@xh/hoist/desktop/cmp/input';
|
|
13
13
|
import {Icon} from '@xh/hoist/icon';
|
|
14
14
|
import {isEmpty} from 'lodash';
|
|
15
|
+
import {fmtJson} from '@xh/hoist/format';
|
|
16
|
+
import {timestampReplacer} from '@xh/hoist/admin/AdminUtils';
|
|
15
17
|
|
|
16
18
|
export const detailsPanel = hoistCmp.factory({
|
|
17
19
|
model: creates(DetailsModel),
|
|
@@ -57,7 +59,7 @@ const stats = hoistCmp.factory<DetailsModel>({
|
|
|
57
59
|
enableSearch: true,
|
|
58
60
|
showFullscreenButton: false,
|
|
59
61
|
editorProps: {lineNumbers: false},
|
|
60
|
-
value:
|
|
62
|
+
value: fmtJson(stats, {replacer: timestampReplacer})
|
|
61
63
|
})
|
|
62
64
|
);
|
|
63
65
|
}
|
|
@@ -6,21 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {ClusterObjectsModel} from '@xh/hoist/admin/tabs/cluster/objects/ClusterObjectsModel';
|
|
8
8
|
import {ColumnSpec, GridModel} from '@xh/hoist/cmp/grid';
|
|
9
|
-
import {HoistModel, lookup, managed,
|
|
9
|
+
import {HoistModel, lookup, managed, XH} from '@xh/hoist/core';
|
|
10
10
|
import {StoreRecord} from '@xh/hoist/data';
|
|
11
|
-
import {fmtDateTimeSec, fmtJson} from '@xh/hoist/format';
|
|
12
11
|
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
cloneDeep,
|
|
16
|
-
forOwn,
|
|
17
|
-
isArray,
|
|
18
|
-
isEmpty,
|
|
19
|
-
isEqual,
|
|
20
|
-
isNumber,
|
|
21
|
-
isPlainObject,
|
|
22
|
-
without
|
|
23
|
-
} from 'lodash';
|
|
12
|
+
import {isEmpty, isEqual, without} from 'lodash';
|
|
13
|
+
import {timestampReplacer} from '@xh/hoist/admin/AdminUtils';
|
|
24
14
|
|
|
25
15
|
export class DetailModel extends HoistModel {
|
|
26
16
|
@lookup(ClusterObjectsModel)
|
|
@@ -66,12 +56,6 @@ export class DetailModel extends HoistModel {
|
|
|
66
56
|
});
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
fmtStats(stats: PlainObject): string {
|
|
70
|
-
stats = cloneDeep(stats);
|
|
71
|
-
this.processTimestamps(stats);
|
|
72
|
-
return fmtJson(JSON.stringify(stats));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
59
|
//----------------------
|
|
76
60
|
// Implementation
|
|
77
61
|
//----------------------
|
|
@@ -95,8 +79,8 @@ export class DetailModel extends HoistModel {
|
|
|
95
79
|
const gridModel = this.createGridModel(diffFields, otherFields);
|
|
96
80
|
gridModel.loadData(
|
|
97
81
|
instanceNames.map(instanceName => {
|
|
98
|
-
|
|
99
|
-
|
|
82
|
+
let data = adminStatsByInstance[instanceName] ?? {};
|
|
83
|
+
data = JSON.parse(JSON.stringify(data, timestampReplacer));
|
|
100
84
|
return {instanceName, ...data};
|
|
101
85
|
})
|
|
102
86
|
);
|
|
@@ -136,23 +120,4 @@ export class DetailModel extends HoistModel {
|
|
|
136
120
|
}
|
|
137
121
|
return ret;
|
|
138
122
|
}
|
|
139
|
-
|
|
140
|
-
private processTimestamps(stats: PlainObject) {
|
|
141
|
-
forOwn(stats, (v, k) => {
|
|
142
|
-
// Convert numbers that look like recent timestamps to date values.
|
|
143
|
-
if (
|
|
144
|
-
(k.endsWith('Time') ||
|
|
145
|
-
k.endsWith('Date') ||
|
|
146
|
-
k.endsWith('Timestamp') ||
|
|
147
|
-
k == 'timestamp') &&
|
|
148
|
-
isNumber(v) &&
|
|
149
|
-
v > Date.now() - 365 * DAYS
|
|
150
|
-
) {
|
|
151
|
-
stats[k] = v ? fmtDateTimeSec(v, {fmt: 'MMM DD HH:mm:ss.SSS'}) : null;
|
|
152
|
-
}
|
|
153
|
-
if (isPlainObject(v) || isArray(v)) {
|
|
154
|
-
this.processTimestamps(v);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
123
|
}
|
|
@@ -12,6 +12,8 @@ import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
|
12
12
|
import {Icon} from '@xh/hoist/icon';
|
|
13
13
|
import {DetailModel} from './DetailModel';
|
|
14
14
|
import './ClusterObjects.scss';
|
|
15
|
+
import {timestampReplacer} from '@xh/hoist/admin/AdminUtils';
|
|
16
|
+
import {fmtJson} from '@xh/hoist/format';
|
|
15
17
|
|
|
16
18
|
export const detailPanel = hoistCmp.factory({
|
|
17
19
|
model: creates(DetailModel),
|
|
@@ -42,7 +44,7 @@ export const detailPanel = hoistCmp.factory({
|
|
|
42
44
|
height: '100%',
|
|
43
45
|
showFullscreenButton: false,
|
|
44
46
|
editorProps: {lineNumbers: false},
|
|
45
|
-
value:
|
|
47
|
+
value: fmtJson(selectedAdminStats, {replacer: timestampReplacer})
|
|
46
48
|
})
|
|
47
49
|
})
|
|
48
50
|
]
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
AlertBannerService,
|
|
26
26
|
AutoRefreshService,
|
|
27
27
|
ChangelogService,
|
|
28
|
+
ClientHealthService,
|
|
28
29
|
ConfigService,
|
|
29
30
|
EnvironmentService,
|
|
30
31
|
FetchService,
|
|
@@ -237,6 +238,7 @@ export class AppContainerModel extends HoistModel {
|
|
|
237
238
|
AlertBannerService,
|
|
238
239
|
AutoRefreshService,
|
|
239
240
|
ChangelogService,
|
|
241
|
+
ClientHealthService,
|
|
240
242
|
IdleService,
|
|
241
243
|
InspectorService,
|
|
242
244
|
GridAutosizeService,
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import {AppState, AppSuspendData, HoistModel, XH} from '@xh/hoist/core';
|
|
8
8
|
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
9
9
|
import {Timer} from '@xh/hoist/utils/async';
|
|
10
|
-
import {getClientDeviceInfo} from '@xh/hoist/utils/js';
|
|
11
10
|
import {camelCase, isBoolean, isString, mapKeys} from 'lodash';
|
|
12
11
|
|
|
13
12
|
/**
|
|
@@ -97,7 +96,7 @@ export class AppStateModel extends HoistModel {
|
|
|
97
96
|
appBuild: XH.appBuild,
|
|
98
97
|
locationHref: window.location.href,
|
|
99
98
|
timings: mapKeys(timings, (v, k) => camelCase(k)),
|
|
100
|
-
|
|
99
|
+
clientHealth: XH.clientHealthService.getReport()
|
|
101
100
|
},
|
|
102
101
|
logData: ['appVersion', 'appBuild'],
|
|
103
102
|
omit: !XH.appSpec.trackAppLoad
|
|
@@ -7,3 +7,7 @@ export declare function exportFilename(moduleName: string): string;
|
|
|
7
7
|
* Returned as a closure to ensure current date is evaluated at export time.
|
|
8
8
|
*/
|
|
9
9
|
export declare function exportFilenameWithDate(moduleName: string): () => string;
|
|
10
|
+
/**
|
|
11
|
+
* Replacer for JSON.stringify to replace timestamp properties with formatted strings.
|
|
12
|
+
*/
|
|
13
|
+
export declare function timestampReplacer(k: string, v: any): any;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { InstancesTabModel } from '@xh/hoist/admin/tabs/cluster/instances/InstancesTabModel';
|
|
3
|
-
import { HoistModel, LoadSpec
|
|
3
|
+
import { HoistModel, LoadSpec } from '@xh/hoist/core';
|
|
4
4
|
export declare class BaseInstanceModel extends HoistModel {
|
|
5
5
|
viewRef: import("react").RefObject<HTMLElement>;
|
|
6
6
|
parent: InstancesTabModel;
|
|
7
7
|
get instanceName(): string;
|
|
8
|
-
fmtStats(stats: PlainObject): string;
|
|
9
8
|
handleLoadException(e: unknown, loadSpec: LoadSpec): void;
|
|
10
9
|
get isVisible(): boolean;
|
|
11
10
|
private isInstanceNotFound;
|
|
12
|
-
private processTimestamps;
|
|
13
11
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ClusterObjectsModel } from '@xh/hoist/admin/tabs/cluster/objects/ClusterObjectsModel';
|
|
2
2
|
import { GridModel } from '@xh/hoist/cmp/grid';
|
|
3
|
-
import { HoistModel
|
|
3
|
+
import { HoistModel } from '@xh/hoist/core';
|
|
4
4
|
import { StoreRecord } from '@xh/hoist/data';
|
|
5
5
|
export declare class DetailModel extends HoistModel {
|
|
6
6
|
parent: ClusterObjectsModel;
|
|
@@ -11,9 +11,7 @@ export declare class DetailModel extends HoistModel {
|
|
|
11
11
|
get instanceName(): string;
|
|
12
12
|
get selectedAdminStats(): any;
|
|
13
13
|
constructor();
|
|
14
|
-
fmtStats(stats: PlainObject): string;
|
|
15
14
|
private updateGridModel;
|
|
16
15
|
private createGridModel;
|
|
17
16
|
private createColSpec;
|
|
18
|
-
private processTimestamps;
|
|
19
17
|
}
|
package/build/types/core/XH.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RouterModel } from '@xh/hoist/appcontainer/RouterModel';
|
|
2
2
|
import { HoistAuthModel } from '@xh/hoist/core/HoistAuthModel';
|
|
3
3
|
import { Store } from '@xh/hoist/data';
|
|
4
|
-
import { AlertBannerService, AutoRefreshService, ChangelogService, ConfigService, EnvironmentService, FetchOptions, FetchService, GridAutosizeService, GridExportService, IdentityService, IdleService, InspectorService, JsonBlobService, LocalStorageService, PrefService, SessionStorageService, TrackService, WebSocketService } from '@xh/hoist/svc';
|
|
4
|
+
import { AlertBannerService, AutoRefreshService, ChangelogService, ConfigService, EnvironmentService, FetchOptions, FetchService, GridAutosizeService, GridExportService, IdentityService, IdleService, InspectorService, JsonBlobService, LocalStorageService, PrefService, SessionStorageService, TrackService, WebSocketService, ClientHealthService } from '@xh/hoist/svc';
|
|
5
5
|
import { Router, State } from 'router5';
|
|
6
6
|
import { CancelFn } from 'router5/types/types/base';
|
|
7
7
|
import { SetOptional } from 'type-fest';
|
|
@@ -47,6 +47,7 @@ export declare class XHApi {
|
|
|
47
47
|
alertBannerService: AlertBannerService;
|
|
48
48
|
autoRefreshService: AutoRefreshService;
|
|
49
49
|
changelogService: ChangelogService;
|
|
50
|
+
clientHealthService: ClientHealthService;
|
|
50
51
|
configService: ConfigService;
|
|
51
52
|
environmentService: EnvironmentService;
|
|
52
53
|
fetchService: FetchService;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
+
import { PlainObject } from '@xh/hoist/core';
|
|
2
3
|
export interface FormatOptions<T = any> {
|
|
3
4
|
/** Display value for null values. */
|
|
4
5
|
nullDisplay?: ReactNode;
|
|
@@ -32,9 +33,9 @@ export interface JSONFormatOptions {
|
|
|
32
33
|
space?: number | string;
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
|
-
* Pretty-print a JSON string, adding line breaks and indentation.
|
|
36
|
+
* Pretty-print a JSON string or (JSON Object), adding line breaks and indentation.
|
|
36
37
|
*/
|
|
37
|
-
export declare function fmtJson(
|
|
38
|
+
export declare function fmtJson(v: string | PlainObject, opts?: JSONFormatOptions): string;
|
|
38
39
|
/**
|
|
39
40
|
* Basic util for splitting a string (via ' ') and capitalizing each word - e.g. for names.
|
|
40
41
|
* Not intended to handle more advanced usages such as HTML or other word boundary characters.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { HoistService, PlainObject } from '@xh/hoist/core';
|
|
2
|
+
/**
|
|
3
|
+
* Service for gathering data about client health.
|
|
4
|
+
*
|
|
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'.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ClientHealthService extends HoistService {
|
|
10
|
+
static instance: ClientHealthService;
|
|
11
|
+
private sources;
|
|
12
|
+
initAsync(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Main Entry report. Return a default report of client health.
|
|
15
|
+
*/
|
|
16
|
+
getReport(): ClientHealthReport;
|
|
17
|
+
/**
|
|
18
|
+
* Register a new source for client health report data. No-op if background health report is
|
|
19
|
+
* not generally enabled via `xhActivityTrackingConfig.clientHealthReport.intervalMins`.
|
|
20
|
+
*
|
|
21
|
+
* @param key - key under which to report the data - can be used to remove this source later.
|
|
22
|
+
* @param callback - function returning serializable to include with each report.
|
|
23
|
+
*/
|
|
24
|
+
addSource(key: string, callback: () => any): void;
|
|
25
|
+
/** Unregister a previously-enabled source for client health report data. */
|
|
26
|
+
removeSource(key: string): void;
|
|
27
|
+
getSession(): SessionData;
|
|
28
|
+
getConnection(): ConnectionData;
|
|
29
|
+
getMemory(): MemoryData;
|
|
30
|
+
getScreen(): ScreenData;
|
|
31
|
+
getWindow(): WindowData;
|
|
32
|
+
getCustom(): PlainObject;
|
|
33
|
+
private sendReport;
|
|
34
|
+
}
|
|
35
|
+
export interface SessionData {
|
|
36
|
+
startTime: number;
|
|
37
|
+
durationMins: number;
|
|
38
|
+
}
|
|
39
|
+
export interface ConnectionData {
|
|
40
|
+
downlink: number;
|
|
41
|
+
effectiveType: string;
|
|
42
|
+
rtt: number;
|
|
43
|
+
}
|
|
44
|
+
export interface MemoryData {
|
|
45
|
+
modelCount: number;
|
|
46
|
+
usedPctLimit?: number;
|
|
47
|
+
jsHeapSizeLimit?: number;
|
|
48
|
+
totalJSHeapSize?: number;
|
|
49
|
+
usedJSHeapSize?: number;
|
|
50
|
+
}
|
|
51
|
+
export interface WindowData {
|
|
52
|
+
devicePixelRatio: number;
|
|
53
|
+
screenX: number;
|
|
54
|
+
screenY: number;
|
|
55
|
+
innerWidth: number;
|
|
56
|
+
innerHeight: number;
|
|
57
|
+
outerWidth: number;
|
|
58
|
+
outerHeight: number;
|
|
59
|
+
}
|
|
60
|
+
export interface ScreenData {
|
|
61
|
+
availWidth: number;
|
|
62
|
+
availHeight: number;
|
|
63
|
+
width: number;
|
|
64
|
+
height: number;
|
|
65
|
+
colorDepth: number;
|
|
66
|
+
pixelDepth: number;
|
|
67
|
+
availLeft: number;
|
|
68
|
+
availTop: number;
|
|
69
|
+
orientation?: {
|
|
70
|
+
angle: number;
|
|
71
|
+
type: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export interface ClientHealthReport {
|
|
75
|
+
session: SessionData;
|
|
76
|
+
connection: ConnectionData;
|
|
77
|
+
memory: MemoryData;
|
|
78
|
+
window: WindowData;
|
|
79
|
+
screen: ScreenData;
|
|
80
|
+
}
|
|
@@ -6,7 +6,6 @@ import { HoistService, TrackOptions } from '@xh/hoist/core';
|
|
|
6
6
|
*/
|
|
7
7
|
export declare class TrackService extends HoistService {
|
|
8
8
|
static instance: TrackService;
|
|
9
|
-
private clientHealthReportSources;
|
|
10
9
|
private oncePerSessionSent;
|
|
11
10
|
private pending;
|
|
12
11
|
initAsync(): Promise<void>;
|
|
@@ -14,21 +13,10 @@ export declare class TrackService extends HoistService {
|
|
|
14
13
|
get enabled(): boolean;
|
|
15
14
|
/** Track User Activity. */
|
|
16
15
|
track(options: TrackOptions | string): void;
|
|
17
|
-
/**
|
|
18
|
-
* Register a new source for client health report data. No-op if background health report is
|
|
19
|
-
* not generally enabled via `xhActivityTrackingConfig.clientHealthReport.intervalMins`.
|
|
20
|
-
*
|
|
21
|
-
* @param key - key under which to report the data - can be used to remove this source later.
|
|
22
|
-
* @param callback - function returning serializable to include with each report.
|
|
23
|
-
*/
|
|
24
|
-
addClientHealthReportSource(key: string, callback: () => any): void;
|
|
25
|
-
/** Unregister a previously-enabled source for client health report data. */
|
|
26
|
-
removeClientHealthReportSource(key: string): void;
|
|
27
16
|
private pushPendingAsync;
|
|
28
17
|
private pushPendingBuffered;
|
|
29
18
|
private toServerJson;
|
|
30
19
|
private logMessage;
|
|
31
|
-
private sendClientHealthReport;
|
|
32
20
|
}
|
|
33
21
|
interface ActivityTrackingConfig {
|
|
34
22
|
clientHealthReport?: Partial<TrackOptions> & {
|
|
@@ -13,5 +13,6 @@ export * from './JsonBlobService';
|
|
|
13
13
|
export * from './PrefService';
|
|
14
14
|
export * from './TrackService';
|
|
15
15
|
export * from './WebSocketService';
|
|
16
|
+
export * from './ClientHealthService';
|
|
16
17
|
export * from './storage/LocalStorageService';
|
|
17
18
|
export * from './storage/SessionStorageService';
|
package/core/XH.ts
CHANGED
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
PrefService,
|
|
29
29
|
SessionStorageService,
|
|
30
30
|
TrackService,
|
|
31
|
-
WebSocketService
|
|
31
|
+
WebSocketService,
|
|
32
|
+
ClientHealthService
|
|
32
33
|
} from '@xh/hoist/svc';
|
|
33
34
|
import {camelCase, flatten, isString, uniqueId} from 'lodash';
|
|
34
35
|
import {Router, State} from 'router5';
|
|
@@ -131,6 +132,7 @@ export class XHApi {
|
|
|
131
132
|
alertBannerService: AlertBannerService;
|
|
132
133
|
autoRefreshService: AutoRefreshService;
|
|
133
134
|
changelogService: ChangelogService;
|
|
135
|
+
clientHealthService: ClientHealthService;
|
|
134
136
|
configService: ConfigService;
|
|
135
137
|
environmentService: EnvironmentService;
|
|
136
138
|
fetchService: FetchService;
|
package/format/FormatMisc.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {span} from '@xh/hoist/cmp/layout';
|
|
8
|
-
import {capitalize, isNil, kebabCase, map} from 'lodash';
|
|
8
|
+
import {capitalize, isNil, isString, kebabCase, map} from 'lodash';
|
|
9
9
|
import {CSSProperties, ReactNode} from 'react';
|
|
10
|
+
import {PlainObject} from '@xh/hoist/core';
|
|
10
11
|
|
|
11
12
|
export interface FormatOptions<T = any> {
|
|
12
13
|
/** Display value for null values. */
|
|
@@ -63,11 +64,12 @@ export interface JSONFormatOptions {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
|
-
* Pretty-print a JSON string, adding line breaks and indentation.
|
|
67
|
+
* Pretty-print a JSON string or (JSON Object), adding line breaks and indentation.
|
|
67
68
|
*/
|
|
68
|
-
export function fmtJson(
|
|
69
|
+
export function fmtJson(v: string | PlainObject, opts?: JSONFormatOptions): string {
|
|
69
70
|
const {replacer = undefined, space = 2} = opts ?? {};
|
|
70
|
-
|
|
71
|
+
if (isString(v)) v = JSON.parse(v);
|
|
72
|
+
return isNil(v) ? '' : JSON.stringify(v, replacer, space);
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "73.0.0-SNAPSHOT.
|
|
3
|
+
"version": "73.0.0-SNAPSHOT.1744147015222",
|
|
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",
|
|
@@ -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
|
|