@xh/hoist 73.0.0-SNAPSHOT.1744229935910 → 73.0.0-SNAPSHOT.1744315607129
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/build/types/security/Types.d.ts +10 -3
- package/build/types/svc/ClientHealthService.d.ts +17 -12
- package/build/types/svc/TrackService.d.ts +5 -1
- package/desktop/appcontainer/AboutDialog.ts +14 -0
- package/kit/onsen/theme.scss +5 -0
- package/mobile/appcontainer/AboutDialog.scss +1 -1
- package/mobile/appcontainer/AboutDialog.ts +24 -1
- package/package.json +1 -1
- package/security/Types.ts +10 -3
- package/security/msal/MsalClient.ts +23 -8
- package/svc/ClientHealthService.ts +29 -16
- package/svc/TrackService.ts +8 -4
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -14,7 +14,14 @@ export interface AccessTokenSpec {
|
|
|
14
14
|
export type TokenMap = Record<string, Token>;
|
|
15
15
|
/** Aggregated telemetry results, produced by {@link MsalClient} when enabled via config. */
|
|
16
16
|
export interface TelemetryResults {
|
|
17
|
-
/** Stats
|
|
17
|
+
/** Stats across all events */
|
|
18
|
+
summary: {
|
|
19
|
+
successCount: number;
|
|
20
|
+
failureCount: number;
|
|
21
|
+
maxDuration: number;
|
|
22
|
+
lastFailureTime: number;
|
|
23
|
+
};
|
|
24
|
+
/** Stats by event type */
|
|
18
25
|
events: Record<string, TelemetryEventResults>;
|
|
19
26
|
}
|
|
20
27
|
/** Aggregated telemetry results for a single type of event. */
|
|
@@ -24,11 +31,11 @@ export interface TelemetryEventResults {
|
|
|
24
31
|
successCount: number;
|
|
25
32
|
failureCount: number;
|
|
26
33
|
/** Timing info (in ms) for event instances reported with duration. */
|
|
27
|
-
duration
|
|
34
|
+
duration?: {
|
|
28
35
|
count: number;
|
|
29
36
|
total: number;
|
|
30
37
|
average: number;
|
|
31
|
-
|
|
38
|
+
max: number;
|
|
32
39
|
};
|
|
33
40
|
lastFailure?: {
|
|
34
41
|
time: number;
|
|
@@ -1,36 +1,41 @@
|
|
|
1
1
|
import { HoistService, PageState, PlainObject } from '@xh/hoist/core';
|
|
2
2
|
/**
|
|
3
|
-
* Service for gathering data about client
|
|
3
|
+
* Service for gathering data about the current state and health of the client app, for submission
|
|
4
|
+
* to the server or review on the console during interactive troubleshooting.
|
|
4
5
|
*
|
|
5
|
-
* Hoist sends this data once on application load
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Hoist sends this data once on application load and can be configured to send at regular intervals
|
|
7
|
+
* throughout a user's session via the `xhActivityTracking.clientHealthReport` app config. Reports
|
|
8
|
+
* are submitted via activity tracking for review within the Admin Console.
|
|
8
9
|
*/
|
|
9
10
|
export declare class ClientHealthService extends HoistService {
|
|
10
11
|
static instance: ClientHealthService;
|
|
11
12
|
private sources;
|
|
12
13
|
initAsync(): Promise<void>;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*/
|
|
14
|
+
get enabled(): boolean;
|
|
15
|
+
/** @returns a customizable report with metrics capturing client app/session state. */
|
|
16
16
|
getReport(): ClientHealthReport;
|
|
17
|
-
/**
|
|
17
|
+
/** @returns a report, formatted for easier viewing in console. **/
|
|
18
18
|
getFormattedReport(): PlainObject;
|
|
19
19
|
/**
|
|
20
|
-
* Register a new source for
|
|
21
|
-
* not generally enabled via `xhActivityTrackingConfig.clientHealthReport.intervalMins`.
|
|
22
|
-
*
|
|
20
|
+
* Register a new source for app-specific data to be sent with each report.
|
|
23
21
|
* @param key - key under which to report the data - can be used to remove this source later.
|
|
24
22
|
* @param callback - function returning serializable to include with each report.
|
|
25
23
|
*/
|
|
26
24
|
addSource(key: string, callback: () => any): void;
|
|
27
25
|
/** Unregister a previously-enabled source for client health report data. */
|
|
28
26
|
removeSource(key: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Generate and submit a report to the server, via TrackService.
|
|
29
|
+
*
|
|
30
|
+
* For ad-hoc troubleshooting. Apps may also configure this service to
|
|
31
|
+
* submit on regular intervals.
|
|
32
|
+
*/
|
|
33
|
+
sendReportAsync(): Promise<void>;
|
|
29
34
|
getGeneral(): GeneralData;
|
|
30
35
|
getConnection(): ConnectionData;
|
|
31
36
|
getMemory(): MemoryData;
|
|
32
37
|
getCustom(): PlainObject;
|
|
33
|
-
private
|
|
38
|
+
private sendReportInternal;
|
|
34
39
|
}
|
|
35
40
|
export interface GeneralData {
|
|
36
41
|
startTime: number;
|
|
@@ -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;
|
|
@@ -42,10 +42,24 @@ export const aboutDialog = hoistCmp.factory({
|
|
|
42
42
|
item: model.getTable()
|
|
43
43
|
}),
|
|
44
44
|
toolbar(
|
|
45
|
+
button({
|
|
46
|
+
text: 'Send Client Health Report',
|
|
47
|
+
icon: Icon.health(),
|
|
48
|
+
omit: !XH.clientHealthService.enabled,
|
|
49
|
+
onClick: async () => {
|
|
50
|
+
try {
|
|
51
|
+
await XH.clientHealthService.sendReportAsync();
|
|
52
|
+
XH.successToast('Client health report successfully submitted.');
|
|
53
|
+
} catch (e) {
|
|
54
|
+
XH.handleException('Error sending client health report', e);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}),
|
|
45
58
|
filler(),
|
|
46
59
|
button({
|
|
47
60
|
text: 'Close',
|
|
48
61
|
intent: 'primary',
|
|
62
|
+
outlined: true,
|
|
49
63
|
onClick: onClose
|
|
50
64
|
})
|
|
51
65
|
)
|
package/kit/onsen/theme.scss
CHANGED
|
@@ -30,7 +30,30 @@ export const aboutDialog = hoistCmp.factory({
|
|
|
30
30
|
className: 'xh-about-dialog',
|
|
31
31
|
item: model.getTable(),
|
|
32
32
|
isOpen: model.isOpen,
|
|
33
|
-
bbar: [
|
|
33
|
+
bbar: [
|
|
34
|
+
button({
|
|
35
|
+
text: 'Send Client Health Report',
|
|
36
|
+
icon: Icon.health(),
|
|
37
|
+
omit: !XH.clientHealthService.enabled,
|
|
38
|
+
onClick: async () => {
|
|
39
|
+
try {
|
|
40
|
+
await XH.clientHealthService.sendReportAsync();
|
|
41
|
+
XH.successToast({
|
|
42
|
+
message: 'Client health report submitted.',
|
|
43
|
+
timeout: 2000
|
|
44
|
+
});
|
|
45
|
+
} catch (e) {
|
|
46
|
+
XH.handleException('Error sending client health report', e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}),
|
|
50
|
+
filler(),
|
|
51
|
+
button({
|
|
52
|
+
text: 'Close',
|
|
53
|
+
outlined: true,
|
|
54
|
+
onClick: () => model.hide()
|
|
55
|
+
})
|
|
56
|
+
]
|
|
34
57
|
});
|
|
35
58
|
}
|
|
36
59
|
});
|
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.1744315607129",
|
|
4
4
|
"description": "Hoist add-on for building and deploying React Applications.",
|
|
5
5
|
"repository": "github:xh/hoist-react",
|
|
6
6
|
"homepage": "https://xh.io",
|
package/security/Types.ts
CHANGED
|
@@ -25,7 +25,14 @@ export type TokenMap = Record<string, Token>;
|
|
|
25
25
|
|
|
26
26
|
/** Aggregated telemetry results, produced by {@link MsalClient} when enabled via config. */
|
|
27
27
|
export interface TelemetryResults {
|
|
28
|
-
/** Stats
|
|
28
|
+
/** Stats across all events */
|
|
29
|
+
summary: {
|
|
30
|
+
successCount: number;
|
|
31
|
+
failureCount: number;
|
|
32
|
+
maxDuration: number;
|
|
33
|
+
lastFailureTime: number;
|
|
34
|
+
};
|
|
35
|
+
/** Stats by event type */
|
|
29
36
|
events: Record<string, TelemetryEventResults>;
|
|
30
37
|
}
|
|
31
38
|
|
|
@@ -36,11 +43,11 @@ export interface TelemetryEventResults {
|
|
|
36
43
|
successCount: number;
|
|
37
44
|
failureCount: number;
|
|
38
45
|
/** Timing info (in ms) for event instances reported with duration. */
|
|
39
|
-
duration
|
|
46
|
+
duration?: {
|
|
40
47
|
count: number;
|
|
41
48
|
total: number;
|
|
42
49
|
average: number;
|
|
43
|
-
|
|
50
|
+
max: number;
|
|
44
51
|
};
|
|
45
52
|
lastFailure?: {
|
|
46
53
|
time: number;
|
|
@@ -115,7 +115,7 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
|
|
|
115
115
|
private initialTokenLoad: boolean;
|
|
116
116
|
|
|
117
117
|
/** Enable telemetry via `enableTelemetry` ctor config, or via {@link enableTelemetry}. */
|
|
118
|
-
telemetryResults: TelemetryResults =
|
|
118
|
+
telemetryResults: TelemetryResults = null;
|
|
119
119
|
private _telemetryCbHandle: string = null;
|
|
120
120
|
|
|
121
121
|
constructor(config: MsalClientConfig) {
|
|
@@ -277,12 +277,20 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
|
|
|
277
277
|
return;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
this.telemetryResults = {
|
|
280
|
+
this.telemetryResults = {
|
|
281
|
+
summary: {
|
|
282
|
+
successCount: 0,
|
|
283
|
+
failureCount: 0,
|
|
284
|
+
maxDuration: 0,
|
|
285
|
+
lastFailureTime: null
|
|
286
|
+
},
|
|
287
|
+
events: {}
|
|
288
|
+
};
|
|
281
289
|
|
|
282
290
|
this._telemetryCbHandle = this.client.addPerformanceCallback(events => {
|
|
283
291
|
events.forEach(e => {
|
|
284
292
|
try {
|
|
285
|
-
const {events} = this.telemetryResults,
|
|
293
|
+
const {summary, events} = this.telemetryResults,
|
|
286
294
|
{name, startTimeMs, durationMs, success, errorName, errorCode} = e,
|
|
287
295
|
eTime = startTimeMs ?? Date.now();
|
|
288
296
|
|
|
@@ -290,15 +298,16 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
|
|
|
290
298
|
firstTime: eTime,
|
|
291
299
|
lastTime: eTime,
|
|
292
300
|
successCount: 0,
|
|
293
|
-
failureCount: 0
|
|
294
|
-
duration: {count: 0, total: 0, average: 0, worst: 0},
|
|
295
|
-
lastFailure: null
|
|
301
|
+
failureCount: 0
|
|
296
302
|
});
|
|
297
303
|
eResult.lastTime = eTime;
|
|
298
304
|
|
|
299
305
|
if (success) {
|
|
306
|
+
summary.successCount++;
|
|
300
307
|
eResult.successCount++;
|
|
301
308
|
} else {
|
|
309
|
+
summary.failureCount++;
|
|
310
|
+
summary.lastFailureTime = eTime;
|
|
302
311
|
eResult.failureCount++;
|
|
303
312
|
eResult.lastFailure = {
|
|
304
313
|
time: eTime,
|
|
@@ -309,11 +318,17 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
|
|
|
309
318
|
}
|
|
310
319
|
|
|
311
320
|
if (durationMs) {
|
|
312
|
-
const
|
|
321
|
+
const duration = (eResult.duration ??= {
|
|
322
|
+
count: 0,
|
|
323
|
+
total: 0,
|
|
324
|
+
average: 0,
|
|
325
|
+
max: 0
|
|
326
|
+
});
|
|
313
327
|
duration.count++;
|
|
314
328
|
duration.total += durationMs;
|
|
315
329
|
duration.average = Math.round(duration.total / duration.count);
|
|
316
|
-
duration.
|
|
330
|
+
duration.max = Math.max(duration.max, durationMs);
|
|
331
|
+
summary.maxDuration = Math.max(summary.maxDuration, durationMs);
|
|
317
332
|
}
|
|
318
333
|
} catch (e) {
|
|
319
334
|
this.logError(`Error processing telemetry event`, e);
|
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {HoistService, PageState, PlainObject, XH} from '@xh/hoist/core';
|
|
7
|
+
import {HoistService, PageState, PlainObject, TrackOptions, XH} from '@xh/hoist/core';
|
|
8
8
|
import {Timer} from '@xh/hoist/utils/async';
|
|
9
9
|
import {MINUTES} from '@xh/hoist/utils/datetime';
|
|
10
10
|
import {withFormattedTimestamps} from '@xh/hoist/format';
|
|
11
11
|
import {pick, round} from 'lodash';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Service for gathering data about client
|
|
14
|
+
* Service for gathering data about the current state and health of the client app, for submission
|
|
15
|
+
* to the server or review on the console during interactive troubleshooting.
|
|
15
16
|
*
|
|
16
|
-
* Hoist sends this data once on application load
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* Hoist sends this data once on application load and can be configured to send at regular intervals
|
|
18
|
+
* throughout a user's session via the `xhActivityTracking.clientHealthReport` app config. Reports
|
|
19
|
+
* are submitted via activity tracking for review within the Admin Console.
|
|
19
20
|
*/
|
|
20
21
|
export class ClientHealthService extends HoistService {
|
|
21
22
|
static instance: ClientHealthService;
|
|
@@ -25,15 +26,17 @@ export class ClientHealthService extends HoistService {
|
|
|
25
26
|
override async initAsync() {
|
|
26
27
|
const {clientHealthReport} = XH.trackService.conf;
|
|
27
28
|
Timer.create({
|
|
28
|
-
runFn: () => this.
|
|
29
|
+
runFn: () => this.sendReportInternal(),
|
|
29
30
|
interval: clientHealthReport.intervalMins * MINUTES,
|
|
30
31
|
delay: true
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
get enabled(): boolean {
|
|
36
|
+
return XH.trackService.enabled;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @returns a customizable report with metrics capturing client app/session state. */
|
|
37
40
|
getReport(): ClientHealthReport {
|
|
38
41
|
return {
|
|
39
42
|
general: this.getGeneral(),
|
|
@@ -43,15 +46,13 @@ export class ClientHealthService extends HoistService {
|
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
/**
|
|
49
|
+
/** @returns a report, formatted for easier viewing in console. **/
|
|
47
50
|
getFormattedReport(): PlainObject {
|
|
48
51
|
return withFormattedTimestamps(this.getReport());
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
/**
|
|
52
|
-
* Register a new source for
|
|
53
|
-
* not generally enabled via `xhActivityTrackingConfig.clientHealthReport.intervalMins`.
|
|
54
|
-
*
|
|
55
|
+
* Register a new source for app-specific data to be sent with each report.
|
|
55
56
|
* @param key - key under which to report the data - can be used to remove this source later.
|
|
56
57
|
* @param callback - function returning serializable to include with each report.
|
|
57
58
|
*/
|
|
@@ -64,6 +65,17 @@ export class ClientHealthService extends HoistService {
|
|
|
64
65
|
this.sources.delete(key);
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Generate and submit a report to the server, via TrackService.
|
|
70
|
+
*
|
|
71
|
+
* For ad-hoc troubleshooting. Apps may also configure this service to
|
|
72
|
+
* submit on regular intervals.
|
|
73
|
+
*/
|
|
74
|
+
async sendReportAsync() {
|
|
75
|
+
this.sendReportInternal({severity: 'INFO'});
|
|
76
|
+
await XH.trackService.pushPendingAsync();
|
|
77
|
+
}
|
|
78
|
+
|
|
67
79
|
// -----------------------------------
|
|
68
80
|
// Generate individual report sections
|
|
69
81
|
//------------------------------------
|
|
@@ -117,16 +129,17 @@ export class ClientHealthService extends HoistService {
|
|
|
117
129
|
return ret;
|
|
118
130
|
}
|
|
119
131
|
|
|
120
|
-
|
|
132
|
+
//---------------------
|
|
121
133
|
// Implementation
|
|
122
|
-
|
|
123
|
-
private
|
|
134
|
+
//---------------------
|
|
135
|
+
private sendReportInternal(opts: Partial<TrackOptions> = {}) {
|
|
124
136
|
const {intervalMins, ...rest} = XH.trackService.conf.clientHealthReport ?? {};
|
|
125
137
|
|
|
126
138
|
XH.track({
|
|
127
139
|
category: 'App',
|
|
128
140
|
message: 'Submitted health report',
|
|
129
141
|
...rest,
|
|
142
|
+
...opts,
|
|
130
143
|
data: {
|
|
131
144
|
clientId: XH.clientId,
|
|
132
145
|
sessionId: XH.sessionId,
|
package/svc/TrackService.ts
CHANGED
|
@@ -90,10 +90,11 @@ export class TrackService extends HoistService {
|
|
|
90
90
|
this.pushPendingBuffered();
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Flush the queue of pending activity tracking messages to the server.
|
|
95
|
+
* @internal - apps should generally allow this service to manage w/its internal debounce.
|
|
96
|
+
*/
|
|
97
|
+
async pushPendingAsync() {
|
|
97
98
|
const {pending} = this;
|
|
98
99
|
if (isEmpty(pending)) return;
|
|
99
100
|
|
|
@@ -105,6 +106,9 @@ export class TrackService extends HoistService {
|
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
//------------------
|
|
110
|
+
// Implementation
|
|
111
|
+
//------------------
|
|
108
112
|
@debounced(10 * SECONDS)
|
|
109
113
|
private pushPendingBuffered() {
|
|
110
114
|
this.pushPendingAsync();
|