@xh/hoist 73.0.0-SNAPSHOT.1744281639439 → 73.0.0-SNAPSHOT.1744315927263
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 +10 -8
- package/admin/tabs/cluster/instances/websocket/WebSocketModel.ts +72 -24
- package/admin/tabs/cluster/instances/websocket/WebSocketPanel.ts +2 -2
- package/build/types/admin/tabs/cluster/instances/websocket/WebSocketModel.d.ts +5 -2
- package/build/types/security/Types.d.ts +10 -3
- package/build/types/svc/ClientHealthService.d.ts +1 -1
- package/build/types/svc/WebSocketService.d.ts +2 -1
- package/package.json +1 -1
- package/security/Types.ts +10 -3
- package/security/msal/MsalClient.ts +23 -8
- package/svc/ClientHealthService.ts +1 -1
- package/svc/WebSocketService.ts +5 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## v73.0.0-SNAPSHOT - unreleased
|
|
4
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
|
+
|
|
5
10
|
## v72.4.0 - 2025-04-09
|
|
6
11
|
|
|
7
12
|
### 🎁 New Features
|
|
8
|
-
|
|
13
|
+
|
|
14
|
+
* Added new methods for formatting timestamps within JSON objects. See `withFormattedTimestamps`
|
|
9
15
|
and `timestampReplacer` in the `@xh/hoist/format` package.
|
|
10
|
-
* `ViewManagerConfig`
|
|
11
|
-
|
|
16
|
+
* Added new `ViewManagerConfig.viewMenuItemFn` option to support custom rendering of pinned views in
|
|
17
|
+
the drop-down menu.
|
|
12
18
|
|
|
13
19
|
### ⚙️ Technical
|
|
20
|
+
|
|
14
21
|
* Added dedicated `ClientHealthService` for managing client health report. Additional enhancements
|
|
15
22
|
to health report to include information about web sockets, idle time, and page state.
|
|
16
23
|
|
|
@@ -30,11 +37,6 @@
|
|
|
30
37
|
|
|
31
38
|
* Improved fetch request tracking to include time spent loading headers as specified by application.
|
|
32
39
|
|
|
33
|
-
### ⚙️ Technical
|
|
34
|
-
|
|
35
|
-
* Update shape of returned `BrowserUtils.getClientDeviceInfo()` to nest several properties under new
|
|
36
|
-
top-level `window` key and report JS heap size / usage values under the `memory` block in MB.
|
|
37
|
-
|
|
38
40
|
### 📚 Libraries
|
|
39
41
|
|
|
40
42
|
* @azure/msal-browser `3.28 → 4.8.0`
|
|
@@ -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 => {
|
|
@@ -107,9 +121,8 @@ export class WebSocketModel extends BaseInstanceModel {
|
|
|
107
121
|
}
|
|
108
122
|
}
|
|
109
123
|
|
|
110
|
-
async forceSuspendAsync() {
|
|
111
|
-
|
|
112
|
-
if (isEmpty(selectedRecords)) return;
|
|
124
|
+
async forceSuspendAsync(toRecs: StoreRecord[]) {
|
|
125
|
+
if (isEmpty(toRecs)) return;
|
|
113
126
|
|
|
114
127
|
const message = await XH.prompt<string>({
|
|
115
128
|
title: 'Please confirm...',
|
|
@@ -123,7 +136,7 @@ export class WebSocketModel extends BaseInstanceModel {
|
|
|
123
136
|
},
|
|
124
137
|
message: div(
|
|
125
138
|
p(
|
|
126
|
-
`This action will force ${
|
|
139
|
+
`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
140
|
),
|
|
128
141
|
p('Enter an optional message below to display within the suspended app.')
|
|
129
142
|
),
|
|
@@ -134,23 +147,58 @@ export class WebSocketModel extends BaseInstanceModel {
|
|
|
134
147
|
});
|
|
135
148
|
|
|
136
149
|
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()}
|
|
150
|
+
await this.bulkPush({
|
|
151
|
+
toRecs,
|
|
152
|
+
topic: XH.webSocketService.FORCE_APP_SUSPEND_TOPIC,
|
|
153
|
+
message,
|
|
154
|
+
trackMessage: 'Suspended clients via WebSocket'
|
|
153
155
|
});
|
|
154
156
|
}
|
|
155
157
|
}
|
|
158
|
+
|
|
159
|
+
async requestHealthReportAsync(toRecs: StoreRecord[]) {
|
|
160
|
+
await this.bulkPush({
|
|
161
|
+
toRecs,
|
|
162
|
+
topic: XH.webSocketService.REQ_CLIENT_HEALTH_RPT_TOPIC
|
|
163
|
+
});
|
|
164
|
+
XH.successToast(
|
|
165
|
+
`Client health report requested for ${pluralize('client', toRecs.length, true)} - available in User Activity shortly...`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
//------------------
|
|
170
|
+
// Implementation
|
|
171
|
+
//------------------
|
|
172
|
+
private async bulkPush({
|
|
173
|
+
toRecs,
|
|
174
|
+
topic,
|
|
175
|
+
message,
|
|
176
|
+
trackMessage
|
|
177
|
+
}: {
|
|
178
|
+
toRecs?: StoreRecord[];
|
|
179
|
+
topic: string;
|
|
180
|
+
message?: string;
|
|
181
|
+
trackMessage?: string;
|
|
182
|
+
}) {
|
|
183
|
+
if (isEmpty(toRecs)) return;
|
|
184
|
+
|
|
185
|
+
const tasks = toRecs.map(rec =>
|
|
186
|
+
XH.fetchJson({
|
|
187
|
+
url: 'webSocketAdmin/pushToChannel',
|
|
188
|
+
params: {
|
|
189
|
+
channelKey: rec.data.key,
|
|
190
|
+
instance: this.instanceName,
|
|
191
|
+
topic,
|
|
192
|
+
message
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
await Promise.allSettled(tasks).track({
|
|
198
|
+
category: 'Audit',
|
|
199
|
+
message: trackMessage,
|
|
200
|
+
data: {users: toRecs.map(it => it.data.user).sort()},
|
|
201
|
+
omit: !trackMessage
|
|
202
|
+
});
|
|
203
|
+
}
|
|
156
204
|
}
|
|
@@ -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'}}),
|
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -27,7 +27,7 @@ export declare class ClientHealthService extends HoistService {
|
|
|
27
27
|
/**
|
|
28
28
|
* Generate and submit a report to the server, via TrackService.
|
|
29
29
|
*
|
|
30
|
-
* For ad-hoc troubleshooting.
|
|
30
|
+
* For ad-hoc troubleshooting. Apps may also configure this service to
|
|
31
31
|
* submit on regular intervals.
|
|
32
32
|
*/
|
|
33
33
|
sendReportAsync(): Promise<void>;
|
|
@@ -27,6 +27,7 @@ export declare class WebSocketService extends HoistService {
|
|
|
27
27
|
readonly HEARTBEAT_TOPIC = "xhHeartbeat";
|
|
28
28
|
readonly REG_SUCCESS_TOPIC = "xhRegistrationSuccess";
|
|
29
29
|
readonly FORCE_APP_SUSPEND_TOPIC = "xhForceAppSuspend";
|
|
30
|
+
readonly REQ_CLIENT_HEALTH_RPT_TOPIC = "xhRequestClientHealthReport";
|
|
30
31
|
/** Unique channel assigned by server upon successful connection. */
|
|
31
32
|
channelKey: string;
|
|
32
33
|
/** Last time a message was received, including heartbeat messages. */
|
|
@@ -69,7 +70,7 @@ export declare class WebSocketService extends HoistService {
|
|
|
69
70
|
onOpen(ev: any): void;
|
|
70
71
|
onClose(ev: any): void;
|
|
71
72
|
onError(ev: any): void;
|
|
72
|
-
onMessage(rawMsg:
|
|
73
|
+
onMessage(rawMsg: MessageEvent): void;
|
|
73
74
|
notifySubscribers(message: any): void;
|
|
74
75
|
getSubsForTopic(topic: any): WebSocketSubscription[];
|
|
75
76
|
updateConnectedStatus(): void;
|
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.1744315927263",
|
|
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);
|
|
@@ -68,7 +68,7 @@ export class ClientHealthService extends HoistService {
|
|
|
68
68
|
/**
|
|
69
69
|
* Generate and submit a report to the server, via TrackService.
|
|
70
70
|
*
|
|
71
|
-
* For ad-hoc troubleshooting.
|
|
71
|
+
* For ad-hoc troubleshooting. Apps may also configure this service to
|
|
72
72
|
* submit on regular intervals.
|
|
73
73
|
*/
|
|
74
74
|
async sendReportAsync() {
|
package/svc/WebSocketService.ts
CHANGED
|
@@ -41,6 +41,7 @@ export class WebSocketService extends HoistService {
|
|
|
41
41
|
readonly HEARTBEAT_TOPIC = 'xhHeartbeat';
|
|
42
42
|
readonly REG_SUCCESS_TOPIC = 'xhRegistrationSuccess';
|
|
43
43
|
readonly FORCE_APP_SUSPEND_TOPIC = 'xhForceAppSuspend';
|
|
44
|
+
readonly REQ_CLIENT_HEALTH_RPT_TOPIC = 'xhRequestClientHealthReport';
|
|
44
45
|
|
|
45
46
|
/** Unique channel assigned by server upon successful connection. */
|
|
46
47
|
@observable
|
|
@@ -210,7 +211,7 @@ export class WebSocketService extends HoistService {
|
|
|
210
211
|
this.updateConnectedStatus();
|
|
211
212
|
}
|
|
212
213
|
|
|
213
|
-
onMessage(rawMsg) {
|
|
214
|
+
onMessage(rawMsg: MessageEvent) {
|
|
214
215
|
try {
|
|
215
216
|
const msg = JSON.parse(rawMsg.data),
|
|
216
217
|
{topic, data} = msg;
|
|
@@ -228,6 +229,9 @@ export class WebSocketService extends HoistService {
|
|
|
228
229
|
XH.suspendApp({reason: 'SERVER_FORCE', message: data});
|
|
229
230
|
XH.track({category: 'App', message: 'App suspended via WebSocket'});
|
|
230
231
|
break;
|
|
232
|
+
case this.REQ_CLIENT_HEALTH_RPT_TOPIC:
|
|
233
|
+
XH.clientHealthService.sendReportAsync();
|
|
234
|
+
break;
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
this.notifySubscribers(msg);
|