@xh/hoist 72.1.0 → 72.2.0

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/admin/tabs/activity/clienterrors/ClientErrorsModel.ts +23 -4
  3. package/admin/tabs/userData/roles/details/RoleDetailsModel.ts +2 -1
  4. package/admin/tabs/userData/roles/recategorize/RecategorizeDialogModel.ts +1 -1
  5. package/build/types/cmp/tab/TabContainerModel.d.ts +5 -5
  6. package/build/types/core/HoistProps.d.ts +1 -0
  7. package/build/types/core/XH.d.ts +5 -5
  8. package/build/types/core/types/Interfaces.d.ts +9 -0
  9. package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +1 -0
  10. package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +1 -0
  11. package/build/types/kit/blueprint/Wrappers.d.ts +1 -1
  12. package/build/types/kit/swiper/index.d.ts +4 -4
  13. package/build/types/security/BaseOAuthClient.d.ts +19 -21
  14. package/build/types/security/Token.d.ts +0 -1
  15. package/build/types/security/Types.d.ts +12 -0
  16. package/build/types/security/authzero/AuthZeroClient.d.ts +3 -4
  17. package/build/types/security/msal/MsalClient.d.ts +3 -4
  18. package/cmp/tab/TabContainerModel.ts +5 -5
  19. package/core/HoistProps.ts +1 -0
  20. package/core/XH.ts +13 -5
  21. package/core/exception/Exception.ts +19 -12
  22. package/core/types/Interfaces.ts +11 -0
  23. package/desktop/appcontainer/ExceptionDialog.ts +1 -1
  24. package/desktop/cmp/dash/canvas/DashCanvas.ts +2 -1
  25. package/package.json +1 -1
  26. package/security/BaseOAuthClient.ts +41 -36
  27. package/security/Token.ts +0 -2
  28. package/security/Types.ts +22 -0
  29. package/security/authzero/AuthZeroClient.ts +6 -8
  30. package/security/msal/MsalClient.ts +6 -8
  31. package/tsconfig.tsbuildinfo +1 -1
  32. package/utils/react/LayoutPropUtils.ts +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## v72.2.0 - 2025-03-13
4
+
5
+ ### 🎁 New Features
6
+ * Modified `TabContainerModel` to make more methods `protected`, improving extensibility for
7
+ advanced use-cases.
8
+ * Enhanced `XH.reloadApp` with new argument to clear query parameters before loading.
9
+ * Enhanced exception handling in `FetchService` to capture messages returned as raw strings, or
10
+ without explicit names.
11
+ * Added dedicated columns to the Admin Console "Client Errors" tab for error names and messages.
12
+ * `BaseOAuthClient` has been enhanced to allow `lazy` loading of Access Tokens, and also made more
13
+ robust such that Access Tokens that fail to load will never prevent the client from
14
+ initialization.
15
+
16
+ ### 🐞 Bug Fixes
17
+
18
+ * Prevent native browser context menu on Dash Canvas surfaces. It can hide the Dash Canvas custom
19
+ context menu when an app's `showBrowserContextMenu` flag is `true`.
20
+
3
21
  ## v72.1.0 - 2025-02-13
4
22
 
5
23
  ### 🎁 New Features
@@ -9,7 +9,7 @@ import * as Col from '@xh/hoist/admin/columns';
9
9
  import {FilterChooserModel} from '@xh/hoist/cmp/filter';
10
10
  import {FormModel} from '@xh/hoist/cmp/form';
11
11
  import {GridModel} from '@xh/hoist/cmp/grid';
12
- import {HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
12
+ import {HoistModel, LoadSpec, managed, PlainObject, XH} from '@xh/hoist/core';
13
13
  import {StoreRecord} from '@xh/hoist/data';
14
14
  import {fmtJson} from '@xh/hoist/format';
15
15
  import {action, bindable, comparer, computed, makeObservable, observable} from '@xh/hoist/mobx';
@@ -62,6 +62,14 @@ export class ClientErrorsModel extends HoistModel {
62
62
  {...Col.appVersion},
63
63
  {...Col.appEnvironment},
64
64
  {...Col.msg, displayName: 'User Message', hidden},
65
+ {
66
+ field: {name: 'errorName', type: 'string'},
67
+ autosizeMaxWidth: 400
68
+ },
69
+ {
70
+ field: {name: 'errorMessage', type: 'string'},
71
+ autosizeMaxWidth: 400
72
+ },
65
73
  {...Col.error, hidden},
66
74
  {...Col.url},
67
75
  {...Col.correlationId},
@@ -119,18 +127,29 @@ export class ClientErrorsModel extends HoistModel {
119
127
  }
120
128
 
121
129
  override async doLoadAsync(loadSpec: LoadSpec) {
122
- const {gridModel} = this;
130
+ const {query, gridModel} = this;
123
131
 
124
132
  try {
125
- const data = await XH.fetchService.postJson({
133
+ const data: PlainObject[] = await XH.fetchService.postJson({
126
134
  url: 'clientErrorAdmin',
127
- body: this.query,
135
+ body: query,
128
136
  loadSpec
129
137
  });
130
138
 
139
+ // Parse name + message from JSON-serialized error object out to top-level properties.
140
+ data.forEach(it => {
141
+ try {
142
+ const error = JSON.parse(it.error);
143
+ it.errorName = error?.name;
144
+ it.errorMessage = error?.message;
145
+ } catch (ignored) {}
146
+ });
147
+
131
148
  gridModel.loadData(data);
132
149
  await gridModel.preSelectFirstAsync();
133
150
  } catch (e) {
151
+ if (loadSpec.isStale || loadSpec.isAutoRefresh) return;
152
+
134
153
  gridModel.clear();
135
154
  XH.handleException(e);
136
155
  }
@@ -44,7 +44,7 @@ export class RoleDetailsModel extends HoistModel {
44
44
  );
45
45
 
46
46
  this.setTabTitle('users', 'Users', role?.effectiveUsers);
47
- this.setTabTitle('directories', 'Directories', role?.effectiveDirectoryGroups);
47
+ this.setTabTitle('directories', 'Dir. Groups', role?.effectiveDirectoryGroups);
48
48
  this.setTabTitle('effectiveRoles', 'Granted To', role?.effectiveRoles);
49
49
  this.setTabTitle('inheritedRoles', 'Inheriting From', role?.inheritedRoles);
50
50
  },
@@ -77,6 +77,7 @@ export class RoleDetailsModel extends HoistModel {
77
77
  },
78
78
  {
79
79
  id: 'directories',
80
+ title: 'Dir. Groups',
80
81
  omit: !this.roleModel.moduleConfig.directoryGroupsSupported,
81
82
  content: directoryMembers
82
83
  },
@@ -65,8 +65,8 @@ export class RecategorizeDialogModel extends HoistModel {
65
65
  }
66
66
  })
67
67
  .linkTo(this.savingTask);
68
- await this.parent.refreshAsync();
69
68
  this.close();
69
+ await this.parent.refreshAsync();
70
70
  } catch (e) {
71
71
  XH.handleException(e);
72
72
  }
@@ -68,7 +68,7 @@ export declare class TabContainerModel extends HoistModel implements Persistable
68
68
  refreshMode: RefreshMode;
69
69
  emptyText: ReactNode;
70
70
  refreshContextModel: RefreshContextModel;
71
- private lastActiveTabId;
71
+ protected lastActiveTabId: string;
72
72
  constructor({ tabs, defaultTabId, route, switcher, track, renderMode, refreshMode, persistWith, emptyText, xhImpl }: TabContainerConfig);
73
73
  /** Set/replace all tabs within the container. */
74
74
  setTabs(tabs: Array<TabModel | TabConfig>): void;
@@ -110,10 +110,10 @@ export declare class TabContainerModel extends HoistModel implements Persistable
110
110
  setPersistableState(state: PersistableState<{
111
111
  activeTabId: string;
112
112
  }>): void;
113
- private setActiveTabIdInternal;
114
- private syncWithRouter;
115
- private forwardRouterToTab;
116
- private calculateActiveTabId;
113
+ protected setActiveTabIdInternal(id: any): void;
114
+ protected syncWithRouter(): void;
115
+ protected forwardRouterToTab(id: any): void;
116
+ protected calculateActiveTabId(tabs: any): any;
117
117
  }
118
118
  export interface AddTabOptions {
119
119
  /** Index in tab collection where tab is to be added. */
@@ -95,6 +95,7 @@ export interface LayoutProps {
95
95
  flexGrow?: string | number;
96
96
  flexShrink?: string | number;
97
97
  flexWrap?: Property.FlexWrap;
98
+ gap?: string | number;
98
99
  alignItems?: string;
99
100
  alignSelf?: string;
100
101
  alignContent?: string;
@@ -9,9 +9,9 @@ import { AppContainerModel } from '../appcontainer/AppContainerModel';
9
9
  import { BannerModel } from '../appcontainer/BannerModel';
10
10
  import { ToastModel } from '../appcontainer/ToastModel';
11
11
  import '../styles/XH.scss';
12
- import { AppSpec, AppState, AppSuspendData, BannerSpec, ExceptionHandler, ExceptionHandlerOptions, HoistAppModel, HoistException, HoistService, HoistServiceClass, HoistUser, MessageSpec, PageState, PlainObject, SizingMode, TaskObserver, Theme, ToastSpec, TrackOptions } from './';
12
+ import { AppSpec, AppState, AppSuspendData, BannerSpec, ExceptionHandler, ExceptionHandlerOptions, HoistAppModel, HoistException, HoistService, HoistServiceClass, HoistUser, MessageSpec, PageState, PlainObject, ReloadAppOptions, SizingMode, TaskObserver, Theme, ToastSpec, TrackOptions } from './';
13
13
  import { HoistModel, ModelSelector, RefreshContextModel } from './model';
14
- export declare const MIN_HOIST_CORE_VERSION = "21.0";
14
+ export declare const MIN_HOIST_CORE_VERSION = "28.0";
15
15
  /**
16
16
  * Top-level Singleton model for Hoist. This is the main entry point for the API.
17
17
  *
@@ -193,13 +193,13 @@ export declare class XHApi {
193
193
  /**
194
194
  * Trigger a full reload of the current application.
195
195
  *
196
- * @param path - relative path to reload (e.g. 'mobile/'). Defaults to the
197
- * existing location pathname.
196
+ * @param opts - options to govern reload. To support legacy usages, a provided
197
+ * string will be treated as `ReloadAppOptions.path`.
198
198
  *
199
199
  * This method will reload the entire application document in the browser - to trigger a
200
200
  * refresh of the loadable content within the app, use {@link refreshAppAsync} instead.
201
201
  */
202
- reloadApp(path?: string): void;
202
+ reloadApp(opts?: ReloadAppOptions | string): void;
203
203
  /**
204
204
  * Refresh the current application.
205
205
  *
@@ -19,6 +19,15 @@ export interface HoistUser {
19
19
  hasRole(s: string): boolean;
20
20
  hasGate(s: string): boolean;
21
21
  }
22
+ /**
23
+ * Options governing XH.reloadApp().
24
+ */
25
+ export interface ReloadAppOptions {
26
+ /** Relative path to reload (e.g. 'mobile/'). Defaults to the existing location pathname. */
27
+ path?: string;
28
+ /** Should the query parameters be removed from the url before reload. Default false. */
29
+ removeQueryParams?: boolean;
30
+ }
22
31
  /**
23
32
  * Options for showing a "toast" notification that appears and then automatically dismisses.
24
33
  */
@@ -61,6 +61,7 @@ export declare const autoRefreshAppOption: ({ formFieldProps, inputProps }?: Aut
61
61
  flexGrow?: string | number;
62
62
  flexShrink?: string | number;
63
63
  flexWrap?: import("csstype").Property.FlexWrap;
64
+ gap?: string | number;
64
65
  alignItems?: string;
65
66
  alignSelf?: string;
66
67
  alignContent?: string;
@@ -59,6 +59,7 @@ export declare const themeAppOption: ({ formFieldProps, inputProps }?: ThemeAppO
59
59
  flexGrow?: string | number;
60
60
  flexShrink?: string | number;
61
61
  flexWrap?: import("csstype").Property.FlexWrap;
62
+ gap?: string | number;
62
63
  alignItems?: string;
63
64
  alignSelf?: string;
64
65
  alignContent?: string;
@@ -2,5 +2,5 @@
2
2
  import { Alert, Button, ButtonGroup, Callout, Card, Checkbox, ControlGroup, Dialog, Drawer, EditableText, FileInput, FormGroup, Hotkey, Hotkeys, InputGroup, Label, Menu, MenuDivider, MenuItem, Navbar, NavbarDivider, NavbarGroup, NavbarHeading, NumericInput, OverflowList, Overlay2 as Overlay, Popover, Radio, RadioGroup, RangeSlider, Slider, Switch, Tab, Tabs, Tag, TagInput, Text, TextArea, Tooltip, Tree } from '@blueprintjs/core';
3
3
  import { DatePicker3 as DatePicker } from '@blueprintjs/datetime2';
4
4
  export { Alert, Button, ButtonGroup, Callout, Card, Checkbox, ControlGroup, DatePicker, Dialog, Drawer, EditableText, FileInput, FormGroup, Hotkeys, Hotkey, InputGroup, Label, Menu, MenuItem, MenuDivider, Navbar, NavbarDivider, NavbarGroup, NavbarHeading, NumericInput, OverflowList, Overlay, Popover, Radio, RadioGroup, RangeSlider, Slider, Switch, Tab, Tabs, Tag, TagInput, TextArea, Text, Tooltip, Tree };
5
- export declare const alert: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").AlertProps>, button: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").ButtonProps>, controlGroup: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").ControlGroupProps>, checkbox: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").CheckboxProps>, dialog: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").DialogProps>, datePicker: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/datetime2").DatePicker3Props>, menuDivider: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").MenuDividerProps>, menuItem: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").MenuItemProps>, navbarDivider: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").NavbarDividerProps>, numericInput: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").HTMLInputProps & import("@blueprintjs/core").NumericInputProps>, overflowList: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").OverflowListProps<any>>, popover: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").PopoverProps<import("@blueprintjs/core").DefaultPopoverTargetHTMLProps>>, radio: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").ControlProps>, rangeSlider: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").RangeSliderProps>, slider: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").SliderProps>, switchControl: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").SwitchProps>, textArea: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TextAreaProps>, tree: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TreeProps<unknown>>, tagInput: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TagInputProps>, fileInput: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").FileInputProps>, overlay: import("@xh/hoist/core").ElementFactory<Pick<import("@blueprintjs/core").Overlay2Props, "lazy" | "className" | "onClose" | "children" | "key" | "autoFocus" | "transitionDuration" | "canEscapeKeyClose" | "enforceFocus" | "shouldReturnFocusOnClose" | "usePortal" | "portalClassName" | "portalContainer" | "portalStopPropagationEvents" | "isOpen" | "transitionName" | "onClosing" | "onClosed" | "onOpening" | "onOpened" | "backdropClassName" | "backdropProps" | "canOutsideClickClose" | "hasBackdrop" | "childRef" | "childRefs"> & import("react").RefAttributes<import("@blueprintjs/core").OverlayInstance>>, tooltip: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TooltipProps<import("@blueprintjs/core").DefaultPopoverTargetHTMLProps>>;
5
+ export declare const alert: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").AlertProps>, button: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").ButtonProps>, controlGroup: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").ControlGroupProps>, checkbox: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").CheckboxProps>, dialog: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").DialogProps>, datePicker: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/datetime2").DatePicker3Props>, menuDivider: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").MenuDividerProps>, menuItem: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").MenuItemProps>, navbarDivider: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").NavbarDividerProps>, numericInput: import("@xh/hoist/core").ElementFactory<Omit<import("@blueprintjs/core").HTMLInputProps, "size"> & import("@blueprintjs/core").NumericInputProps>, overflowList: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").OverflowListProps<any>>, popover: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").PopoverProps<import("@blueprintjs/core").DefaultPopoverTargetHTMLProps>>, radio: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").ControlProps>, rangeSlider: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").RangeSliderProps>, slider: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").SliderProps>, switchControl: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").SwitchProps>, textArea: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TextAreaProps>, tree: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TreeProps<unknown>>, tagInput: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TagInputProps>, fileInput: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").FileInputProps>, overlay: import("@xh/hoist/core").ElementFactory<Omit<import("@blueprintjs/core").Overlay2Props, "ref"> & import("react").RefAttributes<import("@blueprintjs/core").OverlayInstance>>, tooltip: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TooltipProps<import("@blueprintjs/core").DefaultPopoverTargetHTMLProps>>;
6
6
  export declare const buttonGroup: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").ButtonGroupProps>, callout: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").CalloutProps>, card: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").CardProps>, drawer: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").DrawerProps>, editableText: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").EditableTextProps>, formGroup: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").FormGroupProps>, hotkey: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").HotkeyProps>, hotkeys: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").HotkeysProps>, inputGroup: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").InputGroupProps>, label: import("@xh/hoist/core").ElementFactory<import("react").AllHTMLAttributes<HTMLLabelElement> & import("react").RefAttributes<HTMLLabelElement>>, menu: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").MenuProps>, navbar: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").NavbarProps>, navbarHeading: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").NavbarHeadingProps>, navbarGroup: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").NavbarGroupProps>, radioGroup: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").RadioGroupProps>, tabs: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TabsProps>, tab: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TabProps>, tag: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TagProps>, text: import("@xh/hoist/core").ElementFactory<import("@blueprintjs/core").TextProps>;
@@ -13,13 +13,10 @@ export declare const swiper: import("@xh/hoist/core").ElementFactory<import("rea
13
13
  onAutoplayResume?: (swiper: import("swiper/types/swiper-class").default) => void;
14
14
  onAutoplayTimeLeft?: (swiper: import("swiper/types/swiper-class").default, timeLeft: number, percentage: number) => void;
15
15
  onAutoplay?: (swiper: import("swiper/types/swiper-class").default) => void;
16
+ onKeyPress?: (swiper: import("swiper/types/swiper-class").default, keyCode: string) => void;
16
17
  onHashChange?: (swiper: import("swiper/types/swiper-class").default) => void;
17
18
  onHashSet?: (swiper: import("swiper/types/swiper-class").default) => void;
18
- onKeyPress?: (swiper: import("swiper/types/swiper-class").default, keyCode: string) => void;
19
19
  onScroll?: (swiper: import("swiper/types/swiper-class").default, event: WheelEvent) => void;
20
- onScrollbarDragStart?: (swiper: import("swiper/types/swiper-class").default, event: MouseEvent | PointerEvent | TouchEvent) => void;
21
- onScrollbarDragMove?: (swiper: import("swiper/types/swiper-class").default, event: MouseEvent | PointerEvent | TouchEvent) => void;
22
- onScrollbarDragEnd?: (swiper: import("swiper/types/swiper-class").default, event: MouseEvent | PointerEvent | TouchEvent) => void;
23
20
  onNavigationHide?: (swiper: import("swiper/types/swiper-class").default) => void;
24
21
  onNavigationShow?: (swiper: import("swiper/types/swiper-class").default) => void;
25
22
  onNavigationPrev?: (swiper: import("swiper/types/swiper-class").default) => void;
@@ -28,6 +25,9 @@ export declare const swiper: import("@xh/hoist/core").ElementFactory<import("rea
28
25
  onPaginationUpdate?: (swiper: import("swiper/types/swiper-class").default, paginationEl: HTMLElement) => void;
29
26
  onPaginationHide?: (swiper: import("swiper/types/swiper-class").default) => void;
30
27
  onPaginationShow?: (swiper: import("swiper/types/swiper-class").default) => void;
28
+ onScrollbarDragStart?: (swiper: import("swiper/types/swiper-class").default, event: MouseEvent | PointerEvent | TouchEvent) => void;
29
+ onScrollbarDragMove?: (swiper: import("swiper/types/swiper-class").default, event: MouseEvent | PointerEvent | TouchEvent) => void;
30
+ onScrollbarDragEnd?: (swiper: import("swiper/types/swiper-class").default, event: MouseEvent | PointerEvent | TouchEvent) => void;
31
31
  onZoomChange?: (swiper: import("swiper/types/swiper-class").default, scale: number, imageEl: HTMLElement, slideEl: HTMLElement) => void;
32
32
  onInit?: (swiper: import("swiper/types/swiper-class").default) => any;
33
33
  onBeforeDestroy?: (swiper: import("swiper/types/swiper-class").default) => void;
@@ -1,7 +1,8 @@
1
1
  import { HoistBase } from '@xh/hoist/core';
2
- import { Token, TokenMap } from '@xh/hoist/security/Token';
2
+ import { Token } from '@xh/hoist/security/Token';
3
+ import { AccessTokenSpec, TokenMap } from './Types';
3
4
  export type LoginMethod = 'REDIRECT' | 'POPUP';
4
- export interface BaseOAuthClientConfig<S> {
5
+ export interface BaseOAuthClientConfig<S extends AccessTokenSpec> {
5
6
  /** Client ID (GUID) of your app registered with your Oauth provider. */
6
7
  clientId: string;
7
8
  /**
@@ -23,24 +24,16 @@ export interface BaseOAuthClientConfig<S> {
23
24
  * Governs an optional refresh timer that will work to keep the tokens fresh.
24
25
  *
25
26
  * A typical refresh will use the underlying provider cache, and should not result in
26
- * network activity. However, if any token lifetime falls below`autoRefreshSkipCacheSecs`,
27
+ * network activity. However, if any token would expire before the next autoRefresh,
27
28
  * this client will force a call to the underlying provider to get the token.
28
29
  *
29
30
  * In order to allow aging tokens to be replaced in a timely manner, this value should be
30
31
  * significantly shorter than both the minimum token lifetime that will be
31
- * returned by the underlying API and `autoRefreshSkipCacheSecs`.
32
+ * returned by the underlying API.
32
33
  *
33
34
  * Default is -1, disabling this behavior.
34
35
  */
35
36
  autoRefreshSecs?: number;
36
- /**
37
- * During auto-refresh, if the remaining lifetime for any token is below this threshold,
38
- * force the provider to skip the local cache and go directly to the underlying provider for
39
- * new tokens and refresh tokens.
40
- *
41
- * Default is -1, disabling this behavior.
42
- */
43
- autoRefreshSkipCacheSecs?: number;
44
37
  /**
45
38
  * Scopes to request - if any - beyond the core `['openid', 'email']` scopes, which
46
39
  * this client will always request.
@@ -63,13 +56,12 @@ export interface BaseOAuthClientConfig<S> {
63
56
  * suitable concrete implementation to power a client-side OauthService. See `MsalClient` and
64
57
  * `AuthZeroClient`
65
58
  *
66
- * Initialize such a service and this client within the `preAuthInitAsync()` lifecycle method of
67
- * `AppModel` to use the tokens it acquires to authenticate with the Hoist server. (Note this
68
- * requires a suitable server-side `AuthenticationService` implementation to validate the token and
69
- * actually resolve the user.) On init, the client implementation will initiate a pop-up or redirect
70
- * flow as necessary.
59
+ * Initialize such a service and this client within an app's primary {@link HoistAuthModel} to use
60
+ * the tokens it acquires to authenticate with the Hoist server. (Note this requires a suitable
61
+ * server-side `AuthenticationService` implementation to validate the token and actually resolve
62
+ * the user.) On init, the client impl will initiate a pop-up or redirect flow as necessary.
71
63
  */
72
- export declare abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>, S> extends HoistBase {
64
+ export declare abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>, S extends AccessTokenSpec> extends HoistBase {
73
65
  /** Config loaded from UI server + init method. */
74
66
  protected config: C;
75
67
  /** ID Scopes */
@@ -101,9 +93,12 @@ export declare abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>
101
93
  */
102
94
  getAccessTokenAsync(key: string): Promise<Token>;
103
95
  /**
104
- * Get all available tokens.
96
+ * Get all configured tokens.
105
97
  */
106
- getAllTokensAsync(): Promise<TokenMap>;
98
+ getAllTokensAsync(opts?: {
99
+ eagerOnly?: boolean;
100
+ useCache?: boolean;
101
+ }): Promise<TokenMap>;
107
102
  /**
108
103
  * The last authenticated OAuth username.
109
104
  *
@@ -144,7 +139,10 @@ export declare abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>
144
139
  protected restoreRedirectState(key: string): void;
145
140
  /** Call after requesting the provider library redirect the user away for auth. */
146
141
  protected maskAfterRedirectAsync(): Promise<void>;
147
- protected fetchAllTokensAsync(useCache?: boolean): Promise<TokenMap>;
142
+ protected fetchAllTokensAsync(opts?: {
143
+ eagerOnly?: boolean;
144
+ useCache?: boolean;
145
+ }): Promise<TokenMap>;
148
146
  protected getLocalStorage(key: string, defaultValue?: any): any;
149
147
  protected setLocalStorage(key: string, value: any): void;
150
148
  private fetchIdTokenSafeAsync;
@@ -1,5 +1,4 @@
1
1
  import { PlainObject } from '@xh/hoist/core';
2
- export type TokenMap = Record<string, Token>;
3
2
  export declare class Token {
4
3
  readonly value: string;
5
4
  readonly decoded: PlainObject;
@@ -0,0 +1,12 @@
1
+ import { Token } from './Token';
2
+ export type TokenMap = Record<string, Token>;
3
+ export interface AccessTokenSpec {
4
+ /**
5
+ * Mode governing when the access token should be requested from provider.
6
+ * eager (default) - initiate lookup on initialization, but do not block on failure.
7
+ * lazy - lookup when requested by client.
8
+ */
9
+ fetchMode: 'eager' | 'lazy';
10
+ /** Scopes for the desired access token.*/
11
+ scopes: string[];
12
+ }
@@ -1,5 +1,6 @@
1
1
  import type { Auth0ClientOptions } from '@auth0/auth0-spa-js';
2
- import { Token, TokenMap } from '@xh/hoist/security/Token';
2
+ import { Token } from '@xh/hoist/security/Token';
3
+ import { AccessTokenSpec, TokenMap } from '../Types';
3
4
  import { BaseOAuthClient, BaseOAuthClientConfig } from '../BaseOAuthClient';
4
5
  export interface AuthZeroClientConfig extends BaseOAuthClientConfig<AuthZeroTokenSpec> {
5
6
  /** Domain of your app registered with Auth0. */
@@ -24,9 +25,7 @@ export interface AuthZeroClientConfig extends BaseOAuthClientConfig<AuthZeroToke
24
25
  */
25
26
  authZeroClientOptions?: Partial<Auth0ClientOptions>;
26
27
  }
27
- export interface AuthZeroTokenSpec {
28
- /** Scopes for the desired access token.*/
29
- scopes: string[];
28
+ export interface AuthZeroTokenSpec extends AccessTokenSpec {
30
29
  /**
31
30
  * Audience (i.e. API) identifier for AccessToken. Must be registered with Auth0.
32
31
  * Note that this is required to ensure that issued token is a JWT and not an opaque string.
@@ -1,6 +1,7 @@
1
1
  import * as msal from '@azure/msal-browser';
2
2
  import { LogLevel } from '@azure/msal-browser';
3
- import { Token, TokenMap } from '@xh/hoist/security/Token';
3
+ import { Token } from '@xh/hoist/security/Token';
4
+ import { AccessTokenSpec, TokenMap } from '../Types';
4
5
  import { BaseOAuthClient, BaseOAuthClientConfig } from '../BaseOAuthClient';
5
6
  export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
6
7
  /**
@@ -44,9 +45,7 @@ export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
44
45
  */
45
46
  msalClientOptions?: Partial<msal.Configuration>;
46
47
  }
47
- export interface MsalTokenSpec {
48
- /** Scopes for the desired access token. */
49
- scopes: string[];
48
+ export interface MsalTokenSpec extends AccessTokenSpec {
50
49
  /**
51
50
  * Scopes to be added to the scopes requested during interactive and SSO logins.
52
51
  * See the `scopes` property on `PopupRequest`, `RedirectRequest`, and `SSORequest`
@@ -108,7 +108,7 @@ export class TabContainerModel extends HoistModel implements Persistable<{active
108
108
  @managed
109
109
  refreshContextModel: RefreshContextModel;
110
110
 
111
- private lastActiveTabId: string;
111
+ protected lastActiveTabId: string;
112
112
 
113
113
  constructor({
114
114
  tabs = [],
@@ -342,7 +342,7 @@ export class TabContainerModel extends HoistModel implements Persistable<{active
342
342
  // Implementation
343
343
  //-------------------------
344
344
  @action
345
- private setActiveTabIdInternal(id) {
345
+ protected setActiveTabIdInternal(id) {
346
346
  const tab = this.findTab(id);
347
347
  throwIf(!tab, `Unknown Tab ${id} in TabContainer.`);
348
348
  throwIf(tab.disabled, `Cannot activate Tab ${id} because it is disabled!`);
@@ -351,7 +351,7 @@ export class TabContainerModel extends HoistModel implements Persistable<{active
351
351
  this.forwardRouterToTab(id);
352
352
  }
353
353
 
354
- private syncWithRouter() {
354
+ protected syncWithRouter() {
355
355
  const {tabs, route} = this,
356
356
  {router} = XH,
357
357
  state = router.getState();
@@ -364,14 +364,14 @@ export class TabContainerModel extends HoistModel implements Persistable<{active
364
364
  }
365
365
  }
366
366
 
367
- private forwardRouterToTab(id) {
367
+ protected forwardRouterToTab(id) {
368
368
  const {route} = this;
369
369
  if (route && id) {
370
370
  XH.router.forward(route, route + '.' + id);
371
371
  }
372
372
  }
373
373
 
374
- private calculateActiveTabId(tabs) {
374
+ protected calculateActiveTabId(tabs) {
375
375
  let ret;
376
376
 
377
377
  // try route
@@ -118,6 +118,7 @@ export interface LayoutProps {
118
118
  flexGrow?: string | number;
119
119
  flexShrink?: string | number;
120
120
  flexWrap?: Property.FlexWrap;
121
+ gap?: string | number;
121
122
 
122
123
  alignItems?: string;
123
124
  alignSelf?: string;
package/core/XH.ts CHANGED
@@ -54,6 +54,7 @@ import {
54
54
  MessageSpec,
55
55
  PageState,
56
56
  PlainObject,
57
+ ReloadAppOptions,
57
58
  SizingMode,
58
59
  TaskObserver,
59
60
  Theme,
@@ -64,7 +65,7 @@ import {installServicesAsync} from './impl/InstallServices';
64
65
  import {instanceManager} from './impl/InstanceManager';
65
66
  import {HoistModel, ModelSelector, RefreshContextModel} from './model';
66
67
 
67
- export const MIN_HOIST_CORE_VERSION = '21.0';
68
+ export const MIN_HOIST_CORE_VERSION = '28.0';
68
69
 
69
70
  declare const xhAppCode: string;
70
71
  declare const xhAppName: string;
@@ -392,18 +393,25 @@ export class XHApi {
392
393
  /**
393
394
  * Trigger a full reload of the current application.
394
395
  *
395
- * @param path - relative path to reload (e.g. 'mobile/'). Defaults to the
396
- * existing location pathname.
396
+ * @param opts - options to govern reload. To support legacy usages, a provided
397
+ * string will be treated as `ReloadAppOptions.path`.
397
398
  *
398
399
  * This method will reload the entire application document in the browser - to trigger a
399
400
  * refresh of the loadable content within the app, use {@link refreshAppAsync} instead.
400
401
  */
401
402
  @action
402
- reloadApp(path?: string) {
403
+ reloadApp(opts?: ReloadAppOptions | string) {
403
404
  never().linkTo(this.appLoadModel);
405
+
406
+ opts = isString(opts) ? {path: opts} : (opts ?? {});
407
+
404
408
  const {location} = window,
405
- href = path ? `${location.origin}/${path.replace(/^\/+/, '')}` : location.href,
409
+ href = opts.path
410
+ ? `${location.origin}/${opts.path.replace(/^\/+/, '')}`
411
+ : location.href,
406
412
  url = new URL(href);
413
+
414
+ if (opts.removeQueryParams) url.search = '';
407
415
  // Add a unique query param to force a full reload without using the browser cache.
408
416
  url.searchParams.set('xhCacheBuster', Date.now().toString());
409
417
  document.location.assign(url);
@@ -7,7 +7,7 @@
7
7
  import {PlainObject, XH} from '@xh/hoist/core';
8
8
  import {FetchOptions} from '@xh/hoist/svc';
9
9
  import {pluralize} from '@xh/hoist/utils/js';
10
- import {isPlainObject} from 'lodash';
10
+ import {isPlainObject, truncate} from 'lodash';
11
11
  import {FetchException, HoistException, TimeoutException, TimeoutExceptionConfig} from './Types';
12
12
 
13
13
  /**
@@ -90,17 +90,16 @@ export class Exception {
90
90
  // Try to "smart" decode as server provided JSON Exception (with a name)
91
91
  try {
92
92
  const cType = headers.get('Content-Type');
93
- if (cType && cType.includes('application/json')) {
94
- const serverDetails = JSON.parse(responseText);
95
- if (serverDetails?.name) {
96
- return this.createFetchException({
97
- ...defaults,
98
- name: serverDetails.name,
99
- message: serverDetails.message,
100
- isRoutine: serverDetails.isRoutine ?? false,
101
- serverDetails
102
- });
103
- }
93
+ if (cType?.includes('application/json')) {
94
+ const obj = safeParseJson(responseText),
95
+ message = obj ? obj.message : truncate(responseText?.trim(), {length: 255});
96
+ return this.createFetchException({
97
+ ...defaults,
98
+ name: obj?.name ?? defaults.name,
99
+ message: message ?? statusText,
100
+ isRoutine: obj?.isRoutine ?? false,
101
+ serverDetails: obj ?? responseText
102
+ });
104
103
  }
105
104
  } catch (ignored) {}
106
105
 
@@ -222,6 +221,14 @@ export class Exception {
222
221
  }
223
222
  }
224
223
 
224
+ function safeParseJson(txt: string): PlainObject {
225
+ try {
226
+ return JSON.parse(txt);
227
+ } catch (ignored) {
228
+ return null;
229
+ }
230
+ }
231
+
225
232
  export function isHoistException(src: unknown): src is HoistException {
226
233
  return src?.['isHoistException'];
227
234
  }
@@ -28,6 +28,17 @@ export interface HoistUser {
28
28
  hasGate(s: string): boolean;
29
29
  }
30
30
 
31
+ /**
32
+ * Options governing XH.reloadApp().
33
+ */
34
+ export interface ReloadAppOptions {
35
+ /** Relative path to reload (e.g. 'mobile/'). Defaults to the existing location pathname. */
36
+ path?: string;
37
+
38
+ /** Should the query parameters be removed from the url before reload. Default false. */
39
+ removeQueryParams?: boolean;
40
+ }
41
+
31
42
  /**
32
43
  * Options for showing a "toast" notification that appears and then automatically dismisses.
33
44
  */
@@ -79,7 +79,7 @@ export const dismissButton = hoistCmp.factory<ExceptionDialogModel>(({model}) =>
79
79
  icon: Icon.refresh(),
80
80
  text: 'Reload App',
81
81
  autoFocus: true,
82
- onClick: () => XH.reloadApp()
82
+ onClick: () => XH.reloadApp({removeQueryParams: true})
83
83
  })
84
84
  : button({
85
85
  text: 'Close',
@@ -18,7 +18,7 @@ import {
18
18
  import {dashCanvasAddViewButton} from '@xh/hoist/desktop/cmp/button/DashCanvasAddViewButton';
19
19
  import '@xh/hoist/desktop/register';
20
20
  import {Classes, overlay} from '@xh/hoist/kit/blueprint';
21
- import {TEST_ID} from '@xh/hoist/utils/js';
21
+ import {consumeEvent, TEST_ID} from '@xh/hoist/utils/js';
22
22
  import classNames from 'classnames';
23
23
  import ReactGridLayout, {WidthProvider} from 'react-grid-layout';
24
24
  import {DashCanvasModel} from './DashCanvasModel';
@@ -125,6 +125,7 @@ const onContextMenu = (e, model) => {
125
125
  x = clientX + model.ref.current.scrollLeft,
126
126
  y = clientY + model.ref.current.scrollTop;
127
127
 
128
+ consumeEvent(e);
128
129
  showContextMenu(
129
130
  dashCanvasContextMenu({
130
131
  dashCanvasModel: model,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "72.1.0",
3
+ "version": "72.2.0",
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",