@xh/hoist 75.0.0-SNAPSHOT.1753722797428 → 75.0.0-SNAPSHOT.1753727619217

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 CHANGED
@@ -37,6 +37,10 @@
37
37
 
38
38
  ### ⚙️ Technical
39
39
 
40
+ * WebSockets are now enabled by default for client apps, as they have been on the server since Hoist
41
+ Core v20.2. Maintaining a WebSocket connection back to the Hoist server enables useful Admin
42
+ Console functionality and is recommended, but clients that must disable WebSockets can do so via
43
+ `AppSpec.disableWebSockets`. Note `AppSpec.enableWebSockets` is deprecated and can be removed.
40
44
  * Hoist now sets a reference to an app's singleton `AuthModel` on a static `instance` property of
41
45
  the app-specified class. App developers can declare a typed static `instance` property on their
42
46
  model class and use it to access the singleton with its proper type, vs. `XH.authModel`.
@@ -51,6 +55,18 @@
51
55
  * Removed deprecated `FetchService.setDefaultTimeout`
52
56
  * Removed deprecated `IdentityService.logoutAsync`
53
57
 
58
+ ### 📚 Libraries
59
+
60
+ * @auth0/auth0-spa-js `2.1 → 2.3`
61
+ * @azure/msal-browser `4.12 → 4.16`
62
+ * filesize `6.4 → 11.0`
63
+ * mobx-react-lite `4.0 → 4.1`
64
+ * qs `6.13 → 6.14`
65
+ * react-markdown `9.0 → 10.1`
66
+ * regenerator-runtime `0.13 → 0.14`
67
+ * semver `7.6 → 7.7`
68
+ * short-unique-id `5.2 → 5.3`
69
+ * ua-parser-js `1.0 → 2.0`
54
70
 
55
71
  ## v74.1.2 - 2025-07-03
56
72
 
@@ -5,7 +5,7 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {HoistModel} from '@xh/hoist/core';
8
- import parser from 'ua-parser-js';
8
+ import {UAParser} from 'ua-parser-js';
9
9
 
10
10
  /**
11
11
  * Support for user agent parsing.
@@ -32,6 +32,6 @@ export class UserAgentModel extends HoistModel {
32
32
  // Implementation
33
33
  //---------------------
34
34
  private get uaParser() {
35
- return (this._uaParser = this._uaParser ?? new parser());
35
+ return (this._uaParser = this._uaParser ?? new UAParser());
36
36
  }
37
37
  }
@@ -1,4 +1,4 @@
1
- import { HoistAppModel, HoistAuthModel, ElementFactory, HoistProps } from '@xh/hoist/core';
1
+ import { ElementFactory, HoistAppModel, HoistAuthModel, HoistProps } from '@xh/hoist/core';
2
2
  import { Component, ComponentClass, FunctionComponent } from 'react';
3
3
  /**
4
4
  * Spec for a client-side Hoist application. A config matching this class's shape is provided
@@ -50,6 +50,13 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
50
50
  * either `@xh/hoist/desktop/AppContainer` or `@xh/hoist/mobile/AppContainer`.
51
51
  */
52
52
  containerClass: ComponentClass<HoistProps> | FunctionComponent<HoistProps>;
53
+ /**
54
+ * True to disable Hoist's built-in WebSocket support for this client app. Even if the app
55
+ * itself is not using WebSockets for business data, the Hoist Admin Console's "Clients" tab and
56
+ * related functionality benefit from having them enabled, so disable only if there is a good
57
+ * reason to do so.
58
+ */
59
+ disableWebSockets?: boolean;
53
60
  /**
54
61
  * True to disable Field-level XSS protection by default across all Stores/Fields in the app.
55
62
  * For use with secure, internal apps that do not display arbitrary/external user input and
@@ -102,15 +109,16 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
102
109
  * initialized, including a breakdown of elapsed time throughout the init process.
103
110
  */
104
111
  trackAppLoad?: boolean;
105
- /** True to enable Hoist websocket connectivity. */
112
+ /** @deprecated - use {@link AppSpec.disableWebSockets} instead. */
106
113
  webSocketsEnabled?: boolean;
107
- constructor({ authModelClass, checkAccess, clientAppCode, clientAppName, componentClass, containerClass, disableXssProtection, enableLoginForm, enableLogout, idlePanel, isMobileApp, lockoutMessage, lockoutPanel, loginMessage, modelClass, showBrowserContextMenu, trackAppLoad, webSocketsEnabled }: {
114
+ constructor({ authModelClass, checkAccess, clientAppCode, clientAppName, componentClass, containerClass, disableWebSockets, disableXssProtection, enableLoginForm, enableLogout, idlePanel, isMobileApp, lockoutMessage, lockoutPanel, loginMessage, modelClass, showBrowserContextMenu, trackAppLoad, webSocketsEnabled }: {
108
115
  authModelClass?: typeof HoistAuthModel;
109
116
  checkAccess: any;
110
117
  clientAppCode?: string;
111
118
  clientAppName?: string;
112
119
  componentClass: any;
113
120
  containerClass: any;
121
+ disableWebSockets?: boolean;
114
122
  disableXssProtection?: boolean;
115
123
  enableLoginForm?: boolean;
116
124
  enableLogout?: boolean;
@@ -122,6 +130,6 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
122
130
  modelClass: any;
123
131
  showBrowserContextMenu?: boolean;
124
132
  trackAppLoad?: boolean;
125
- webSocketsEnabled?: boolean;
133
+ webSocketsEnabled: any;
126
134
  });
127
135
  }
@@ -15,18 +15,18 @@ export declare const swiper: import("@xh/hoist/core").ElementFactory<import("rea
15
15
  onHashChange?: (swiper: import("swiper/react").SwiperClass) => void;
16
16
  onHashSet?: (swiper: import("swiper/react").SwiperClass) => void;
17
17
  onKeyPress?: (swiper: import("swiper/react").SwiperClass, keyCode: string) => void;
18
- onScroll?: (swiper: import("swiper/react").SwiperClass, event: WheelEvent) => void;
19
18
  onNavigationHide?: (swiper: import("swiper/react").SwiperClass) => void;
20
19
  onNavigationShow?: (swiper: import("swiper/react").SwiperClass) => void;
21
20
  onNavigationPrev?: (swiper: import("swiper/react").SwiperClass) => void;
22
21
  onNavigationNext?: (swiper: import("swiper/react").SwiperClass) => void;
22
+ onScroll?: (swiper: import("swiper/react").SwiperClass, event: WheelEvent) => void;
23
+ onScrollbarDragStart?: (swiper: import("swiper/react").SwiperClass, event: MouseEvent | TouchEvent | PointerEvent) => void;
24
+ onScrollbarDragMove?: (swiper: import("swiper/react").SwiperClass, event: MouseEvent | TouchEvent | PointerEvent) => void;
25
+ onScrollbarDragEnd?: (swiper: import("swiper/react").SwiperClass, event: MouseEvent | TouchEvent | PointerEvent) => void;
23
26
  onPaginationRender?: (swiper: import("swiper/react").SwiperClass, paginationEl: HTMLElement) => void;
24
27
  onPaginationUpdate?: (swiper: import("swiper/react").SwiperClass, paginationEl: HTMLElement) => void;
25
28
  onPaginationHide?: (swiper: import("swiper/react").SwiperClass) => void;
26
29
  onPaginationShow?: (swiper: import("swiper/react").SwiperClass) => void;
27
- onScrollbarDragStart?: (swiper: import("swiper/react").SwiperClass, event: MouseEvent | TouchEvent | PointerEvent) => void;
28
- onScrollbarDragMove?: (swiper: import("swiper/react").SwiperClass, event: MouseEvent | TouchEvent | PointerEvent) => void;
29
- onScrollbarDragEnd?: (swiper: import("swiper/react").SwiperClass, event: MouseEvent | TouchEvent | PointerEvent) => void;
30
30
  onZoomChange?: (swiper: import("swiper/react").SwiperClass, scale: number, imageEl: HTMLElement, slideEl: HTMLElement) => void;
31
31
  onInit?: (swiper: import("swiper/react").SwiperClass) => any;
32
32
  onBeforeDestroy?: (swiper: import("swiper/react").SwiperClass) => void;
@@ -24,14 +24,14 @@ import { HoistService, PlainObject } from '@xh/hoist/core';
24
24
  */
25
25
  export declare class WebSocketService extends HoistService {
26
26
  static instance: WebSocketService;
27
- readonly HEARTBEAT_TOPIC = "xhHeartbeat";
28
27
  /** Check connection and send a new heartbeat (which should be promptly ack'd) every 10s. */
28
+ readonly HEARTBEAT_TOPIC = "xhHeartbeat";
29
29
  readonly HEARTBEAT_INTERVAL: number;
30
30
  readonly REG_SUCCESS_TOPIC = "xhRegistrationSuccess";
31
31
  readonly FORCE_APP_SUSPEND_TOPIC = "xhForceAppSuspend";
32
32
  readonly REQ_CLIENT_HEALTH_RPT_TOPIC = "xhRequestClientHealthReport";
33
33
  readonly METADATA_FOR_HANDSHAKE: string[];
34
- /** True if WebSockets generally enabled - set statically in code via {@link AppSpec}. */
34
+ /** True if WebSockets not explicitly disabled via {@link AppSpec.disableWebSockets}. */
35
35
  enabled: boolean;
36
36
  /** Unique channel assigned by server upon successful connection. */
37
37
  channelKey: string;
@@ -15,7 +15,7 @@
15
15
  border: 1px solid var(--xh-intent-danger);
16
16
  max-width: 50%;
17
17
  text-align: center;
18
- word-break: break-word;
18
+ overflow-wrap: break-word;
19
19
 
20
20
  & > * {
21
21
  margin: 0 0 var(--xh-pad-px);
@@ -5,10 +5,7 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  .xh-tile-frame {
8
- // Scrollbar will be shown above the content in order to not take up width.
9
- // Consider increasing the spacing for TileFrames that are expected to scroll.
10
- overflow-y: overlay !important;
11
- overflow-x: overlay !important;
8
+ overflow: auto !important;
12
9
 
13
10
  &__tile {
14
11
  position: absolute !important;
package/core/AppSpec.ts CHANGED
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {XH, HoistAppModel, HoistAuthModel, ElementFactory, HoistProps} from '@xh/hoist/core';
8
- import {throwIf} from '@xh/hoist/utils/js';
9
- import {isFunction, isNil, isString} from 'lodash';
7
+ import {ElementFactory, HoistAppModel, HoistAuthModel, HoistProps, XH} from '@xh/hoist/core';
8
+ import {apiDeprecated, throwIf} from '@xh/hoist/utils/js';
9
+ import {isFunction, isNil, isString, isUndefined} from 'lodash';
10
10
  import {Component, ComponentClass, FunctionComponent} from 'react';
11
11
 
12
12
  /**
@@ -62,6 +62,14 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
62
62
  */
63
63
  containerClass: ComponentClass<HoistProps> | FunctionComponent<HoistProps>;
64
64
 
65
+ /**
66
+ * True to disable Hoist's built-in WebSocket support for this client app. Even if the app
67
+ * itself is not using WebSockets for business data, the Hoist Admin Console's "Clients" tab and
68
+ * related functionality benefit from having them enabled, so disable only if there is a good
69
+ * reason to do so.
70
+ */
71
+ disableWebSockets?: boolean;
72
+
65
73
  /**
66
74
  * True to disable Field-level XSS protection by default across all Stores/Fields in the app.
67
75
  * For use with secure, internal apps that do not display arbitrary/external user input and
@@ -125,7 +133,7 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
125
133
  */
126
134
  trackAppLoad?: boolean;
127
135
 
128
- /** True to enable Hoist websocket connectivity. */
136
+ /** @deprecated - use {@link AppSpec.disableWebSockets} instead. */
129
137
  webSocketsEnabled?: boolean;
130
138
 
131
139
  constructor({
@@ -135,6 +143,7 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
135
143
  clientAppName = XH.appName,
136
144
  componentClass,
137
145
  containerClass,
146
+ disableWebSockets = false,
138
147
  disableXssProtection = false,
139
148
  enableLoginForm = false,
140
149
  enableLogout = false,
@@ -146,7 +155,7 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
146
155
  modelClass,
147
156
  showBrowserContextMenu = false,
148
157
  trackAppLoad = true,
149
- webSocketsEnabled = false
158
+ webSocketsEnabled
150
159
  }) {
151
160
  throwIf(!componentClass, 'A Hoist App must define a componentClass');
152
161
 
@@ -164,12 +173,24 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
164
173
  'A Hoist App must specify a required role string or a function for checkAccess.'
165
174
  );
166
175
 
176
+ if (!isUndefined(webSocketsEnabled)) {
177
+ let msg: string;
178
+ if (webSocketsEnabled === false) {
179
+ disableWebSockets = true;
180
+ msg = `Specify disableWebSockets: true to continue actively disabling WebSockets if required.`;
181
+ } else {
182
+ msg = `WebSockets are now enabled by default - this property can be safely removed from your appSpec.`;
183
+ }
184
+ apiDeprecated('webSocketsEnabled', {msg, v: 'v78'});
185
+ }
186
+
167
187
  this.authModelClass = authModelClass;
168
188
  this.checkAccess = checkAccess;
169
189
  this.clientAppCode = clientAppCode;
170
190
  this.clientAppName = clientAppName;
171
191
  this.componentClass = componentClass;
172
192
  this.containerClass = containerClass;
193
+ this.disableWebSockets = disableWebSockets;
173
194
  this.disableXssProtection = disableXssProtection;
174
195
  this.enableLoginForm = enableLoginForm;
175
196
  this.enableLogout = enableLogout;
@@ -181,6 +202,6 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
181
202
  this.modelClass = modelClass;
182
203
  this.showBrowserContextMenu = showBrowserContextMenu;
183
204
  this.trackAppLoad = trackAppLoad;
184
- this.webSocketsEnabled = webSocketsEnabled;
205
+ this.webSocketsEnabled = !disableWebSockets;
185
206
  }
186
207
  }
package/core/XH.ts CHANGED
@@ -386,7 +386,6 @@ export class XHApi {
386
386
  clientAppCode: 'admin',
387
387
  clientAppName: `${this.appName} Admin`,
388
388
  isMobileApp: false,
389
- webSocketsEnabled: true,
390
389
  checkAccess: 'HOIST_ADMIN_READER',
391
390
  ...appSpec
392
391
  });
@@ -30,7 +30,7 @@
30
30
  }
31
31
 
32
32
  .bp5-toast-message {
33
- word-break: break-word;
33
+ overflow-wrap: break-word;
34
34
  }
35
35
 
36
36
  // High level of specificity here required to override built-in BP styles
@@ -11,7 +11,7 @@ import '@xh/hoist/desktop/register';
11
11
  import {Icon} from '@xh/hoist/icon';
12
12
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
13
13
  import {isEmpty} from 'codemirror/src/util/misc';
14
- import filesize from 'filesize';
14
+ import {filesize} from 'filesize';
15
15
  import {find, uniqBy, without} from 'lodash';
16
16
 
17
17
  export class FileChooserModel extends HoistModel {
@@ -51,12 +51,12 @@
51
51
  // See https://github.com/xh/hoist-react/issues/862 for why this is necessary. Note need for
52
52
  // datepicker workaround below - indicates that this might be a problematic solution in general
53
53
  // to the original issue, but after a few years that's been the only reported issue, so...
54
- word-break: break-word;
54
+ overflow-wrap: break-word;
55
55
 
56
56
  // Ensure that date numbers in the datepicker do not wrap if the datePicker portal is rendered
57
57
  // inside a dialogBody - e.g. `dateEditor` in a grid in a dialog. See comment on #862.
58
58
  .bp5-datepicker {
59
- word-break: normal;
59
+ overflow-wrap: normal;
60
60
  }
61
61
  }
62
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "75.0.0-SNAPSHOT.1753722797428",
3
+ "version": "75.0.0-SNAPSHOT.1753727619217",
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",
@@ -28,8 +28,8 @@
28
28
  ]
29
29
  },
30
30
  "dependencies": {
31
- "@auth0/auth0-spa-js": "~2.1.3",
32
- "@azure/msal-browser": "~4.12.0",
31
+ "@auth0/auth0-spa-js": "~2.3.0",
32
+ "@azure/msal-browser": "~4.16.0",
33
33
  "@blueprintjs/core": "^5.10.5",
34
34
  "@blueprintjs/datetime": "^5.3.7",
35
35
  "@blueprintjs/datetime2": "^2.3.7",
@@ -39,19 +39,19 @@
39
39
  "@fortawesome/pro-regular-svg-icons": "^6.6.0",
40
40
  "@fortawesome/pro-solid-svg-icons": "^6.6.0",
41
41
  "@fortawesome/pro-thin-svg-icons": "^6.6.0",
42
- "@fortawesome/react-fontawesome": "^0.2.2",
42
+ "@fortawesome/react-fontawesome": "^0.2.3",
43
43
  "@onsenui/fastclick": "~1.1.1",
44
44
  "@popperjs/core": "~2.11.0",
45
45
  "@seznam/compose-react-refs": "~1.0.5",
46
46
  "classnames": "~2.5.1",
47
47
  "clipboard-copy": "~4.0.1",
48
48
  "codemirror": "~5.65.0",
49
- "core-js": "^3.0",
49
+ "core-js": "3.x",
50
50
  "debounce-promise": "~3.1.0",
51
51
  "dompurify": "~3.2.3",
52
52
  "downloadjs": "~1.4.7",
53
53
  "fast-deep-equal": "~3.1.1",
54
- "filesize": "~6.4.0",
54
+ "filesize": "~11.0.2",
55
55
  "golden-layout": "~1.5.9",
56
56
  "http-status-codes": "~2.3.0",
57
57
  "inter-ui": "~3.19.3",
@@ -59,32 +59,32 @@
59
59
  "lodash": "~4.17.21",
60
60
  "lodash-inflection": "~1.5.0",
61
61
  "mobx": "~6.13.2",
62
- "mobx-react-lite": "~4.0.7",
62
+ "mobx-react-lite": "~4.1.0",
63
63
  "moment": "~2.30.1",
64
64
  "numbro": "~2.5.0",
65
65
  "onsenui": "~2.12.8",
66
- "qs": "~6.13.0",
66
+ "qs": "~6.14.0",
67
67
  "react-beautiful-dnd": "~13.1.0",
68
68
  "react-dates": "~21.8.0",
69
69
  "react-dropzone": "~10.2.2",
70
70
  "react-grid-layout": "1.5.0",
71
- "react-markdown": "~9.0.1",
71
+ "react-markdown": "~10.1.0",
72
72
  "react-onsenui": "~1.13.2",
73
73
  "react-popper": "~2.3.0",
74
74
  "react-select": "~4.3.1",
75
75
  "react-transition-group": "~4.4.2",
76
76
  "react-windowed-select": "~3.1.2",
77
- "regenerator-runtime": "~0.13.11",
77
+ "regenerator-runtime": "~0.14.1",
78
78
  "remark-breaks": "~4.0.0",
79
79
  "remark-gfm": "~4.0.0",
80
80
  "resize-observer-polyfill": "~1.5.1",
81
81
  "router5": "~7.0.2",
82
82
  "router5-plugin-browser": "~7.0.2",
83
- "semver": "~7.6.0",
84
- "short-unique-id": "~5.2.0",
83
+ "semver": "~7.7.2",
84
+ "short-unique-id": "~5.3.2",
85
85
  "store2": "~2.14.3",
86
86
  "swiper": "^11.2.0",
87
- "ua-parser-js": "~1.0.2"
87
+ "ua-parser-js": "~2.0.4"
88
88
  },
89
89
  "peerDependencies": {
90
90
  "react": "~18.2.0",
@@ -102,13 +102,13 @@
102
102
  "eslint-config-prettier": "10.x",
103
103
  "eslint-plugin-tsdoc": "0.x",
104
104
  "husky": "9.x",
105
- "lint-staged": "15.x",
105
+ "lint-staged": "16.x",
106
106
  "postcss": "8.x",
107
107
  "prettier": "3.x",
108
108
  "react": "~18.2.0",
109
109
  "react-dom": "~18.2.0",
110
110
  "stylelint": "16.x",
111
- "stylelint-config-standard-scss": "14.x",
111
+ "stylelint-config-standard-scss": "15.x",
112
112
  "type-fest": "4.x",
113
113
  "typescript": "~5.8.3"
114
114
  },
@@ -38,8 +38,8 @@ import {find, pull} from 'lodash';
38
38
  export class WebSocketService extends HoistService {
39
39
  static instance: WebSocketService;
40
40
 
41
- readonly HEARTBEAT_TOPIC = 'xhHeartbeat';
42
41
  /** Check connection and send a new heartbeat (which should be promptly ack'd) every 10s. */
42
+ readonly HEARTBEAT_TOPIC = 'xhHeartbeat';
43
43
  readonly HEARTBEAT_INTERVAL = 10 * SECONDS;
44
44
 
45
45
  readonly REG_SUCCESS_TOPIC = 'xhRegistrationSuccess';
@@ -47,8 +47,8 @@ export class WebSocketService extends HoistService {
47
47
  readonly REQ_CLIENT_HEALTH_RPT_TOPIC = 'xhRequestClientHealthReport';
48
48
  readonly METADATA_FOR_HANDSHAKE = ['appVersion', 'appBuild', 'loadId', 'tabId'];
49
49
 
50
- /** True if WebSockets generally enabled - set statically in code via {@link AppSpec}. */
51
- enabled: boolean = XH.appSpec.webSocketsEnabled;
50
+ /** True if WebSockets not explicitly disabled via {@link AppSpec.disableWebSockets}. */
51
+ enabled: boolean = !XH.appSpec.disableWebSockets;
52
52
 
53
53
  /** Unique channel assigned by server upon successful connection. */
54
54
  @observable channelKey: string = null;
@@ -84,7 +84,7 @@ export class WebSocketService extends HoistService {
84
84
  const {environmentService} = XH;
85
85
  if (environmentService.get('webSocketsEnabled') === false) {
86
86
  this.logError(
87
- `WebSockets enabled on this client app but disabled on server. Adjust your server-side config.`
87
+ `WebSockets enabled on this client app but disabled on server - unexpected! WebSockets will not be available, review and reconcile your server configuration.`
88
88
  );
89
89
  this.enabled = false;
90
90
  return;