@backstage/core-app-api 1.2.1-next.2 → 1.3.0-next.4

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
@@ -1,5 +1,77 @@
1
1
  # @backstage/core-app-api
2
2
 
3
+ ## 1.3.0-next.4
4
+
5
+ ### Minor Changes
6
+
7
+ - e0d9c9559a: Added a new `AppRouter` component and `app.createRoot()` method that replaces `app.getRouter()` and `app.getProvider()`, which are now deprecated. The new `AppRouter` component is a drop-in replacement for the old router component, while the new `app.createRoot()` method is used instead of the old provider component.
8
+
9
+ An old app setup might look like this:
10
+
11
+ ```tsx
12
+ const app = createApp(/* ... */);
13
+
14
+ const AppProvider = app.getProvider();
15
+ const AppRouter = app.getRouter();
16
+
17
+ const routes = ...;
18
+
19
+ const App = () => (
20
+ <AppProvider>
21
+ <AlertDisplay />
22
+ <OAuthRequestDialog />
23
+ <AppRouter>
24
+ <Root>{routes}</Root>
25
+ </AppRouter>
26
+ </AppProvider>
27
+ );
28
+
29
+ export default App;
30
+ ```
31
+
32
+ With these new APIs, the setup now looks like this:
33
+
34
+ ```tsx
35
+ import { AppRouter } from '@backstage/core-app-api';
36
+
37
+ const app = createApp(/* ... */);
38
+
39
+ const routes = ...;
40
+
41
+ export default app.createRoot(
42
+ <>
43
+ <AlertDisplay />
44
+ <OAuthRequestDialog />
45
+ <AppRouter>
46
+ <Root>{routes}</Root>
47
+ </AppRouter>
48
+ </>,
49
+ );
50
+ ```
51
+
52
+ Note that `app.createRoot()` accepts a React element, rather than a component.
53
+
54
+ ### Patch Changes
55
+
56
+ - b05dcd5530: Move the `zod` dependency to a version that does not collide with other libraries
57
+ - Updated dependencies
58
+ - @backstage/config@1.0.5-next.1
59
+ - @backstage/core-plugin-api@1.2.0-next.2
60
+ - @backstage/types@1.0.2-next.1
61
+ - @backstage/version-bridge@1.0.3-next.0
62
+
63
+ ## 1.2.1-next.3
64
+
65
+ ### Patch Changes
66
+
67
+ - 6870b43dd1: Fix for the automatic rewriting of base URLs.
68
+ - 653d7912ac: Made `WebStorage` notify its subscribers when `localStorage` values change in other tabs/windows
69
+ - Updated dependencies
70
+ - @backstage/config@1.0.5-next.1
71
+ - @backstage/core-plugin-api@1.2.0-next.2
72
+ - @backstage/types@1.0.2-next.1
73
+ - @backstage/version-bridge@1.0.3-next.0
74
+
3
75
  ## 1.2.1-next.2
4
76
 
5
77
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -535,22 +535,45 @@ declare class WebStorage implements StorageApi {
535
535
  private readonly namespace;
536
536
  private readonly errorApi;
537
537
  constructor(namespace: string, errorApi: ErrorApi);
538
+ private static hasSubscribed;
538
539
  static create(options: {
539
540
  errorApi: ErrorApi;
540
541
  namespace?: string;
541
542
  }): WebStorage;
543
+ private static addStorageEventListener;
542
544
  get<T>(key: string): T | undefined;
543
545
  snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T>;
544
546
  forBucket(name: string): WebStorage;
545
547
  set<T>(key: string, data: T): Promise<void>;
546
548
  remove(key: string): Promise<void>;
547
549
  observe$<T extends JsonValue>(key: string): Observable<StorageValueSnapshot<T>>;
550
+ private handleStorageChange;
548
551
  private getKeyName;
549
552
  private notifyChanges;
550
553
  private subscribers;
551
554
  private readonly observable;
552
555
  }
553
556
 
557
+ /**
558
+ * Props for the {@link AppRouter} component.
559
+ * @public
560
+ */
561
+ interface AppRouterProps {
562
+ children?: ReactNode;
563
+ }
564
+ /**
565
+ * App router and sign-in page wrapper.
566
+ *
567
+ * @public
568
+ * @remarks
569
+ *
570
+ * The AppRouter provides the routing context and renders the sign-in page.
571
+ * Until the user has successfully signed in, this component will render
572
+ * the sign-in page. Once the user has signed-in, it will instead render
573
+ * the app, while providing routing and route tracking for the app.
574
+ */
575
+ declare function AppRouter(props: AppRouterProps): JSX.Element;
576
+
554
577
  /**
555
578
  * Props for the `BootErrorPage` component of {@link AppComponents}.
556
579
  *
@@ -797,14 +820,42 @@ declare type BackstageApp = {
797
820
  * Get a common or custom icon for this app.
798
821
  */
799
822
  getSystemIcon(key: string): IconComponent | undefined;
823
+ /**
824
+ * Creates the root component that renders the entire app.
825
+ *
826
+ * @remarks
827
+ *
828
+ * This method must only be called once, and you have to provide it the entire
829
+ * app element tree. The element tree will be analyzed to discover plugins,
830
+ * routes, and other app features. The returned component will render all
831
+ * of the app elements wrapped within the app context provider.
832
+ *
833
+ * @example
834
+ * ```tsx
835
+ * export default app.createRoot(
836
+ * <>
837
+ * <AlertDisplay />
838
+ * <OAuthRequestDialog />
839
+ * <AppRouter>
840
+ * <Root>{routes}</Root>
841
+ * </AppRouter>
842
+ * </>,
843
+ * );
844
+ * ```
845
+ */
846
+ createRoot(element: JSX.Element): ComponentType<{}>;
800
847
  /**
801
848
  * Provider component that should wrap the Router created with getRouter()
802
849
  * and any other components that need to be within the app context.
850
+ *
851
+ * @deprecated Use {@link BackstageApp.createRoot} instead.
803
852
  */
804
853
  getProvider(): ComponentType<{}>;
805
854
  /**
806
855
  * Router component that should wrap the App Routes create with getRoutes()
807
856
  * and any other components that should only be available while signed in.
857
+ *
858
+ * @deprecated Import and use the {@link AppRouter} component from `@backstage/core-app-api` instead
808
859
  */
809
860
  getRouter(): ComponentType<{}>;
810
861
  };
@@ -904,4 +955,4 @@ declare type FeatureFlaggedProps = {
904
955
  */
905
956
  declare const FeatureFlagged: (props: FeatureFlaggedProps) => JSX.Element;
906
957
 
907
- export { AlertApiForwarder, ApiFactoryHolder, ApiFactoryRegistry, ApiFactoryScope, ApiProvider, ApiProviderProps, ApiResolver, AppComponents, AppConfigLoader, AppContext, AppIcons, AppOptions, AppRouteBinder, AppThemeSelector, AtlassianAuth, AuthApiCreateOptions, BackstageApp, BitbucketAuth, BitbucketSession, BootErrorPageProps, ErrorAlerter, ErrorApiForwarder, ErrorBoundaryFallbackProps, FeatureFlagged, FeatureFlaggedProps, FetchMiddleware, FetchMiddlewares, FlatRoutes, FlatRoutesProps, GithubAuth, GitlabAuth, GoogleAuth, LocalStorageFeatureFlags, MicrosoftAuth, MultipleAnalyticsApi, NoOpAnalyticsApi, OAuth2, OAuth2CreateOptions, OAuth2Session, OAuthApiCreateOptions, OAuthRequestManager, OktaAuth, OneLoginAuth, OneLoginAuthCreateOptions, SamlAuth, SignInPageProps, UnhandledErrorForwarder, UrlPatternDiscovery, WebStorage, createFetchApi, createSpecializedApp, defaultConfigLoader };
958
+ export { AlertApiForwarder, ApiFactoryHolder, ApiFactoryRegistry, ApiFactoryScope, ApiProvider, ApiProviderProps, ApiResolver, AppComponents, AppConfigLoader, AppContext, AppIcons, AppOptions, AppRouteBinder, AppRouter, AppRouterProps, AppThemeSelector, AtlassianAuth, AuthApiCreateOptions, BackstageApp, BitbucketAuth, BitbucketSession, BootErrorPageProps, ErrorAlerter, ErrorApiForwarder, ErrorBoundaryFallbackProps, FeatureFlagged, FeatureFlaggedProps, FetchMiddleware, FetchMiddlewares, FlatRoutes, FlatRoutesProps, GithubAuth, GitlabAuth, GoogleAuth, LocalStorageFeatureFlags, MicrosoftAuth, MultipleAnalyticsApi, NoOpAnalyticsApi, OAuth2, OAuth2CreateOptions, OAuth2Session, OAuthApiCreateOptions, OAuthRequestManager, OktaAuth, OneLoginAuth, OneLoginAuthCreateOptions, SamlAuth, SignInPageProps, UnhandledErrorForwarder, UrlPatternDiscovery, WebStorage, createFetchApi, createSpecializedApp, defaultConfigLoader };
package/dist/index.esm.js CHANGED
@@ -1,12 +1,12 @@
1
- import React, { useContext, Children, isValidElement, useEffect, useMemo, useState, createContext, useRef } from 'react';
1
+ import React, { useContext, createContext, useEffect, useState, Children, isValidElement, useMemo, useRef } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { createVersionedContext, createVersionedValueMap, getOrCreateGlobalSingleton } from '@backstage/version-bridge';
4
4
  import ObservableImpl from 'zen-observable';
5
- import { SessionState, FeatureFlagState, getComponentData, attachComponentData, useApi, featureFlagsApiRef, AnalyticsContext, useAnalytics, appThemeApiRef, configApiRef, identityApiRef, useApp, useElementFilter } from '@backstage/core-plugin-api';
5
+ import { SessionState, FeatureFlagState, AnalyticsContext, useAnalytics, useApp, useApi, configApiRef, getComponentData, attachComponentData, featureFlagsApiRef, appThemeApiRef, identityApiRef, useElementFilter } from '@backstage/core-plugin-api';
6
6
  import { z } from 'zod';
7
7
  import { ConfigReader } from '@backstage/config';
8
8
  export { ConfigReader } from '@backstage/config';
9
- import { matchRoutes, generatePath, useLocation, createRoutesFromChildren, Route, Routes, useRoutes } from 'react-router-dom';
9
+ import { createRoutesFromChildren, Route, useLocation, matchRoutes, Routes, generatePath, useRoutes } from 'react-router-dom';
10
10
  import useAsync from 'react-use/lib/useAsync';
11
11
  import useObservable from 'react-use/lib/useObservable';
12
12
 
@@ -1613,7 +1613,7 @@ class OAuthRequestManager {
1613
1613
  }
1614
1614
 
1615
1615
  const buckets = /* @__PURE__ */ new Map();
1616
- class WebStorage {
1616
+ const _WebStorage = class {
1617
1617
  constructor(namespace, errorApi) {
1618
1618
  this.namespace = namespace;
1619
1619
  this.errorApi = errorApi;
@@ -1627,7 +1627,17 @@ class WebStorage {
1627
1627
  }
1628
1628
  static create(options) {
1629
1629
  var _a;
1630
- return new WebStorage((_a = options.namespace) != null ? _a : "", options.errorApi);
1630
+ return new _WebStorage((_a = options.namespace) != null ? _a : "", options.errorApi);
1631
+ }
1632
+ static addStorageEventListener() {
1633
+ window.addEventListener("storage", (event) => {
1634
+ var _a;
1635
+ for (const [bucketPath, webStorage] of buckets.entries()) {
1636
+ if ((_a = event.key) == null ? void 0 : _a.startsWith(bucketPath)) {
1637
+ webStorage.handleStorageChange(event.key);
1638
+ }
1639
+ }
1640
+ });
1631
1641
  }
1632
1642
  get(key) {
1633
1643
  return this.snapshot(key).value;
@@ -1656,7 +1666,7 @@ class WebStorage {
1656
1666
  forBucket(name) {
1657
1667
  const bucketPath = `${this.namespace}/${name}`;
1658
1668
  if (!buckets.has(bucketPath)) {
1659
- buckets.set(bucketPath, new WebStorage(bucketPath, this.errorApi));
1669
+ buckets.set(bucketPath, new _WebStorage(bucketPath, this.errorApi));
1660
1670
  }
1661
1671
  return buckets.get(bucketPath);
1662
1672
  }
@@ -1669,8 +1679,21 @@ class WebStorage {
1669
1679
  this.notifyChanges(key);
1670
1680
  }
1671
1681
  observe$(key) {
1682
+ if (!_WebStorage.hasSubscribed) {
1683
+ _WebStorage.addStorageEventListener();
1684
+ _WebStorage.hasSubscribed = true;
1685
+ }
1672
1686
  return this.observable.filter(({ key: messageKey }) => messageKey === key);
1673
1687
  }
1688
+ handleStorageChange(eventKey) {
1689
+ if (!(eventKey == null ? void 0 : eventKey.startsWith(this.namespace))) {
1690
+ return;
1691
+ }
1692
+ const trimmedKey = eventKey == null ? void 0 : eventKey.slice(`${this.namespace}/`.length);
1693
+ if (!trimmedKey.includes("/")) {
1694
+ this.notifyChanges(decodeURIComponent(trimmedKey));
1695
+ }
1696
+ }
1674
1697
  getKeyName(key) {
1675
1698
  return `${this.namespace}/${encodeURIComponent(key)}`;
1676
1699
  }
@@ -1680,6 +1703,147 @@ class WebStorage {
1680
1703
  subscription.next(snapshot);
1681
1704
  }
1682
1705
  }
1706
+ };
1707
+ let WebStorage = _WebStorage;
1708
+ WebStorage.hasSubscribed = false;
1709
+
1710
+ const InternalAppContext = createContext(void 0);
1711
+
1712
+ function isReactRouterBeta() {
1713
+ const [obj] = createRoutesFromChildren(/* @__PURE__ */ React.createElement(Route, { index: true, element: /* @__PURE__ */ React.createElement("div", null) }));
1714
+ return !obj.index;
1715
+ }
1716
+
1717
+ const getExtensionContext = (pathname, routes) => {
1718
+ var _a, _b;
1719
+ try {
1720
+ const matches = matchRoutes(routes, { pathname });
1721
+ const routeObject = (_a = matches == null ? void 0 : matches.filter((match) => {
1722
+ var _a2;
1723
+ return ((_a2 = match == null ? void 0 : match.route.routeRefs) == null ? void 0 : _a2.size) > 0;
1724
+ }).pop()) == null ? void 0 : _a.route;
1725
+ if (!routeObject) {
1726
+ return {};
1727
+ }
1728
+ let routeRef;
1729
+ if (routeObject.routeRefs.size === 1) {
1730
+ routeRef = routeObject.routeRefs.values().next().value;
1731
+ }
1732
+ return {
1733
+ extension: "App",
1734
+ pluginId: ((_b = routeObject.plugin) == null ? void 0 : _b.getId()) || "root",
1735
+ ...routeRef ? { routeRef: routeRef.id } : {}
1736
+ };
1737
+ } catch {
1738
+ return {};
1739
+ }
1740
+ };
1741
+ const TrackNavigation = ({
1742
+ pathname,
1743
+ search,
1744
+ hash
1745
+ }) => {
1746
+ const analytics = useAnalytics();
1747
+ useEffect(() => {
1748
+ analytics.captureEvent("navigate", `${pathname}${search}${hash}`);
1749
+ }, [analytics, pathname, search, hash]);
1750
+ return null;
1751
+ };
1752
+ const RouteTracker = ({
1753
+ routeObjects
1754
+ }) => {
1755
+ const { pathname, search, hash } = useLocation();
1756
+ return /* @__PURE__ */ React.createElement(AnalyticsContext, { attributes: getExtensionContext(pathname, routeObjects) }, /* @__PURE__ */ React.createElement(TrackNavigation, { pathname, search, hash }));
1757
+ };
1758
+
1759
+ function getBasePath(configApi) {
1760
+ if (!isReactRouterBeta()) {
1761
+ return "";
1762
+ }
1763
+ return readBasePath(configApi);
1764
+ }
1765
+ function readBasePath(configApi) {
1766
+ var _a;
1767
+ let { pathname } = new URL(
1768
+ (_a = configApi.getOptionalString("app.baseUrl")) != null ? _a : "/",
1769
+ "http://sample.dev"
1770
+ );
1771
+ pathname = pathname.replace(/\/*$/, "");
1772
+ return pathname;
1773
+ }
1774
+ function SignInPageWrapper({
1775
+ component: Component,
1776
+ appIdentityProxy,
1777
+ children
1778
+ }) {
1779
+ const [identityApi, setIdentityApi] = useState();
1780
+ const configApi = useApi(configApiRef);
1781
+ const basePath = getBasePath(configApi);
1782
+ if (!identityApi) {
1783
+ return /* @__PURE__ */ React.createElement(Component, { onSignInSuccess: setIdentityApi });
1784
+ }
1785
+ appIdentityProxy.setTarget(identityApi, {
1786
+ signOutTargetUrl: basePath || "/"
1787
+ });
1788
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
1789
+ }
1790
+ function AppRouter(props) {
1791
+ const { Router: RouterComponent, SignInPage: SignInPageComponent } = useApp().getComponents();
1792
+ const configApi = useApi(configApiRef);
1793
+ const basePath = readBasePath(configApi);
1794
+ const mountPath = `${basePath}/*`;
1795
+ const internalAppContext = useContext(InternalAppContext);
1796
+ if (!internalAppContext) {
1797
+ throw new Error("AppRouter must be rendered within the AppProvider");
1798
+ }
1799
+ const { routeObjects, appIdentityProxy } = internalAppContext;
1800
+ if (!SignInPageComponent) {
1801
+ appIdentityProxy.setTarget(
1802
+ {
1803
+ getUserId: () => "guest",
1804
+ getIdToken: async () => void 0,
1805
+ getProfile: () => ({
1806
+ email: "guest@example.com",
1807
+ displayName: "Guest"
1808
+ }),
1809
+ getProfileInfo: async () => ({
1810
+ email: "guest@example.com",
1811
+ displayName: "Guest"
1812
+ }),
1813
+ getBackstageIdentity: async () => ({
1814
+ type: "user",
1815
+ userEntityRef: "user:default/guest",
1816
+ ownershipEntityRefs: ["user:default/guest"]
1817
+ }),
1818
+ getCredentials: async () => ({}),
1819
+ signOut: async () => {
1820
+ }
1821
+ },
1822
+ { signOutTargetUrl: basePath || "/" }
1823
+ );
1824
+ if (isReactRouterBeta()) {
1825
+ return /* @__PURE__ */ React.createElement(RouterComponent, null, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: mountPath, element: /* @__PURE__ */ React.createElement(React.Fragment, null, props.children) })));
1826
+ }
1827
+ return /* @__PURE__ */ React.createElement(RouterComponent, { basename: basePath }, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), props.children);
1828
+ }
1829
+ if (isReactRouterBeta()) {
1830
+ return /* @__PURE__ */ React.createElement(RouterComponent, null, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(
1831
+ SignInPageWrapper,
1832
+ {
1833
+ component: SignInPageComponent,
1834
+ appIdentityProxy
1835
+ },
1836
+ /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: mountPath, element: /* @__PURE__ */ React.createElement(React.Fragment, null, props.children) }))
1837
+ ));
1838
+ }
1839
+ return /* @__PURE__ */ React.createElement(RouterComponent, { basename: basePath }, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(
1840
+ SignInPageWrapper,
1841
+ {
1842
+ component: SignInPageComponent,
1843
+ appIdentityProxy
1844
+ },
1845
+ props.children
1846
+ ));
1683
1847
  }
1684
1848
 
1685
1849
  function traverseElementTree(options) {
@@ -2173,48 +2337,6 @@ const RoutingProvider = ({
2173
2337
  return /* @__PURE__ */ React.createElement(RoutingContext.Provider, { value: versionedValue }, children);
2174
2338
  };
2175
2339
 
2176
- const getExtensionContext = (pathname, routes) => {
2177
- var _a, _b;
2178
- try {
2179
- const matches = matchRoutes(routes, { pathname });
2180
- const routeObject = (_a = matches == null ? void 0 : matches.filter((match) => {
2181
- var _a2;
2182
- return ((_a2 = match == null ? void 0 : match.route.routeRefs) == null ? void 0 : _a2.size) > 0;
2183
- }).pop()) == null ? void 0 : _a.route;
2184
- if (!routeObject) {
2185
- return {};
2186
- }
2187
- let routeRef;
2188
- if (routeObject.routeRefs.size === 1) {
2189
- routeRef = routeObject.routeRefs.values().next().value;
2190
- }
2191
- return {
2192
- extension: "App",
2193
- pluginId: ((_b = routeObject.plugin) == null ? void 0 : _b.getId()) || "root",
2194
- ...routeRef ? { routeRef: routeRef.id } : {}
2195
- };
2196
- } catch {
2197
- return {};
2198
- }
2199
- };
2200
- const TrackNavigation = ({
2201
- pathname,
2202
- search,
2203
- hash
2204
- }) => {
2205
- const analytics = useAnalytics();
2206
- useEffect(() => {
2207
- analytics.captureEvent("navigate", `${pathname}${search}${hash}`);
2208
- }, [analytics, pathname, search, hash]);
2209
- return null;
2210
- };
2211
- const RouteTracker = ({
2212
- routeObjects
2213
- }) => {
2214
- const { pathname, search, hash } = useLocation();
2215
- return /* @__PURE__ */ React.createElement(AnalyticsContext, { attributes: getExtensionContext(pathname, routeObjects) }, /* @__PURE__ */ React.createElement(TrackNavigation, { pathname, search, hash }));
2216
- };
2217
-
2218
2340
  function validateRouteParameters(routePaths, routeParents) {
2219
2341
  const notLeafRoutes = new Set(routeParents.values());
2220
2342
  notLeafRoutes.delete(void 0);
@@ -2493,27 +2615,25 @@ function resolveRouteBindings(bindRoutes) {
2493
2615
  return result;
2494
2616
  }
2495
2617
 
2496
- function isReactRouterBeta() {
2497
- const [obj] = createRoutesFromChildren(/* @__PURE__ */ React.createElement(Route, { index: true, element: /* @__PURE__ */ React.createElement("div", null) }));
2498
- return !obj.index;
2499
- }
2500
-
2501
- const InternalAppContext = createContext({ routeObjects: [] });
2502
- function getBasePath(configApi) {
2503
- if (!isReactRouterBeta()) {
2504
- return "";
2505
- }
2506
- return readBasePath(configApi);
2507
- }
2508
- function readBasePath(configApi) {
2509
- var _a;
2510
- let { pathname } = new URL(
2511
- (_a = configApi.getOptionalString("app.baseUrl")) != null ? _a : "/",
2512
- "http://sample.dev"
2513
- );
2514
- pathname = pathname.replace(/\/*$/, "");
2515
- return pathname;
2516
- }
2618
+ var __accessCheck = (obj, member, msg) => {
2619
+ if (!member.has(obj))
2620
+ throw TypeError("Cannot " + msg);
2621
+ };
2622
+ var __privateGet = (obj, member, getter) => {
2623
+ __accessCheck(obj, member, "read from private field");
2624
+ return getter ? getter.call(obj) : member.get(obj);
2625
+ };
2626
+ var __privateAdd = (obj, member, value) => {
2627
+ if (member.has(obj))
2628
+ throw TypeError("Cannot add the same private member more than once");
2629
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
2630
+ };
2631
+ var __privateSet = (obj, member, value, setter) => {
2632
+ __accessCheck(obj, member, "write to private field");
2633
+ setter ? setter.call(obj, value) : member.set(obj, value);
2634
+ return value;
2635
+ };
2636
+ var _getProviderCalled;
2517
2637
  function useConfigLoader(configLoader, components, appThemeApi) {
2518
2638
  var _a;
2519
2639
  const hasConfig = Boolean(configLoader);
@@ -2540,7 +2660,7 @@ function useConfigLoader(configLoader, components, appThemeApi) {
2540
2660
  return new URL(
2541
2661
  fullUrl.replace(getOrigin(fullUrl), ""),
2542
2662
  document.location.origin
2543
- ).href;
2663
+ ).href.replace(/\/$/, "");
2544
2664
  };
2545
2665
  const appBaseUrl = urlConfigReader.getOptionalString("app.baseUrl");
2546
2666
  const backendBaseUrl = urlConfigReader.getOptionalString("backend.baseUrl");
@@ -2553,15 +2673,17 @@ function useConfigLoader(configLoader, components, appThemeApi) {
2553
2673
  const appOrigin = getOrigin(appBaseUrl);
2554
2674
  const backendOrigin = getOrigin(backendBaseUrl);
2555
2675
  if (appOrigin === backendOrigin) {
2556
- relativeResolverConfig.data.backend = {
2557
- baseUrl: overrideOrigin(backendBaseUrl)
2558
- };
2676
+ const newBackendBaseUrl = overrideOrigin(backendBaseUrl);
2677
+ if (backendBaseUrl !== newBackendBaseUrl) {
2678
+ relativeResolverConfig.data.backend = { baseUrl: newBackendBaseUrl };
2679
+ }
2559
2680
  }
2560
2681
  }
2561
2682
  if (appBaseUrl) {
2562
- relativeResolverConfig.data.app = {
2563
- baseUrl: overrideOrigin(appBaseUrl)
2564
- };
2683
+ const newAppBaseUrl = overrideOrigin(appBaseUrl);
2684
+ if (appBaseUrl !== newAppBaseUrl) {
2685
+ relativeResolverConfig.data.app = { baseUrl: newAppBaseUrl };
2686
+ }
2565
2687
  }
2566
2688
  if (Object.keys(relativeResolverConfig.data).length) {
2567
2689
  configs = configs.concat([relativeResolverConfig]);
@@ -2592,6 +2714,7 @@ class AppContextImpl {
2592
2714
  class AppManager {
2593
2715
  constructor(options) {
2594
2716
  this.appIdentityProxy = new AppIdentityProxy();
2717
+ __privateAdd(this, _getProviderCalled, false);
2595
2718
  var _a, _b, _c, _d;
2596
2719
  this.apis = (_a = options.apis) != null ? _a : [];
2597
2720
  this.icons = options.icons;
@@ -2615,7 +2738,20 @@ class AppManager {
2615
2738
  getComponents() {
2616
2739
  return this.components;
2617
2740
  }
2741
+ createRoot(element) {
2742
+ const AppProvider = this.getProvider();
2743
+ const AppRoot = () => {
2744
+ return /* @__PURE__ */ React.createElement(AppProvider, null, element);
2745
+ };
2746
+ return AppRoot;
2747
+ }
2618
2748
  getProvider() {
2749
+ if (__privateGet(this, _getProviderCalled)) {
2750
+ throw new Error(
2751
+ "app.getProvider() or app.createRoot() has already been called, and can only be called once"
2752
+ );
2753
+ }
2754
+ __privateSet(this, _getProviderCalled, true);
2619
2755
  const appContext = new AppContextImpl(this);
2620
2756
  let routesHaveBeenValidated = false;
2621
2757
  const Provider = ({ children }) => {
@@ -2708,7 +2844,10 @@ class AppManager {
2708
2844
  /* @__PURE__ */ React.createElement(
2709
2845
  InternalAppContext.Provider,
2710
2846
  {
2711
- value: { routeObjects: routing.objects }
2847
+ value: {
2848
+ routeObjects: routing.objects,
2849
+ appIdentityProxy: this.appIdentityProxy
2850
+ }
2712
2851
  },
2713
2852
  children
2714
2853
  )
@@ -2717,61 +2856,6 @@ class AppManager {
2717
2856
  return Provider;
2718
2857
  }
2719
2858
  getRouter() {
2720
- const { Router: RouterComponent, SignInPage: SignInPageComponent } = this.components;
2721
- const SignInPageWrapper = ({
2722
- component: Component,
2723
- children
2724
- }) => {
2725
- const [identityApi, setIdentityApi] = useState();
2726
- const configApi = useApi(configApiRef);
2727
- const basePath = getBasePath(configApi);
2728
- if (!identityApi) {
2729
- return /* @__PURE__ */ React.createElement(Component, { onSignInSuccess: setIdentityApi });
2730
- }
2731
- this.appIdentityProxy.setTarget(identityApi, {
2732
- signOutTargetUrl: basePath || "/"
2733
- });
2734
- return children;
2735
- };
2736
- const AppRouter = ({ children }) => {
2737
- const configApi = useApi(configApiRef);
2738
- const basePath = readBasePath(configApi);
2739
- const mountPath = `${basePath}/*`;
2740
- const { routeObjects } = useContext(InternalAppContext);
2741
- if (!SignInPageComponent) {
2742
- this.appIdentityProxy.setTarget(
2743
- {
2744
- getUserId: () => "guest",
2745
- getIdToken: async () => void 0,
2746
- getProfile: () => ({
2747
- email: "guest@example.com",
2748
- displayName: "Guest"
2749
- }),
2750
- getProfileInfo: async () => ({
2751
- email: "guest@example.com",
2752
- displayName: "Guest"
2753
- }),
2754
- getBackstageIdentity: async () => ({
2755
- type: "user",
2756
- userEntityRef: "user:default/guest",
2757
- ownershipEntityRefs: ["user:default/guest"]
2758
- }),
2759
- getCredentials: async () => ({}),
2760
- signOut: async () => {
2761
- }
2762
- },
2763
- { signOutTargetUrl: basePath || "/" }
2764
- );
2765
- if (isReactRouterBeta()) {
2766
- return /* @__PURE__ */ React.createElement(RouterComponent, null, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: mountPath, element: /* @__PURE__ */ React.createElement(React.Fragment, null, children) })));
2767
- }
2768
- return /* @__PURE__ */ React.createElement(RouterComponent, { basename: basePath }, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), children);
2769
- }
2770
- if (isReactRouterBeta()) {
2771
- return /* @__PURE__ */ React.createElement(RouterComponent, null, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(SignInPageWrapper, { component: SignInPageComponent }, /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: mountPath, element: /* @__PURE__ */ React.createElement(React.Fragment, null, children) }))));
2772
- }
2773
- return /* @__PURE__ */ React.createElement(RouterComponent, { basename: basePath }, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(SignInPageWrapper, { component: SignInPageComponent }, /* @__PURE__ */ React.createElement(React.Fragment, null, children)));
2774
- };
2775
2859
  return AppRouter;
2776
2860
  }
2777
2861
  getApiHolder() {
@@ -2853,6 +2937,7 @@ class AppManager {
2853
2937
  }
2854
2938
  }
2855
2939
  }
2940
+ _getProviderCalled = new WeakMap();
2856
2941
 
2857
2942
  function createSpecializedApp(options) {
2858
2943
  return new AppManager(options);
@@ -2906,5 +2991,5 @@ const FlatRoutes = (props) => {
2906
2991
  return useRoutes(withNotFound);
2907
2992
  };
2908
2993
 
2909
- export { AlertApiForwarder, ApiFactoryRegistry, ApiProvider, ApiResolver, AppThemeSelector, AtlassianAuth, BitbucketAuth, ErrorAlerter, ErrorApiForwarder, FeatureFlagged, FetchMiddlewares, FlatRoutes, GithubAuth, GitlabAuth, GoogleAuth, LocalStorageFeatureFlags, MicrosoftAuth, MultipleAnalyticsApi, NoOpAnalyticsApi, OAuth2, OAuthRequestManager, OktaAuth, OneLoginAuth, SamlAuth, UnhandledErrorForwarder, UrlPatternDiscovery, WebStorage, createFetchApi, createSpecializedApp, defaultConfigLoader };
2994
+ export { AlertApiForwarder, ApiFactoryRegistry, ApiProvider, ApiResolver, AppRouter, AppThemeSelector, AtlassianAuth, BitbucketAuth, ErrorAlerter, ErrorApiForwarder, FeatureFlagged, FetchMiddlewares, FlatRoutes, GithubAuth, GitlabAuth, GoogleAuth, LocalStorageFeatureFlags, MicrosoftAuth, MultipleAnalyticsApi, NoOpAnalyticsApi, OAuth2, OAuthRequestManager, OktaAuth, OneLoginAuth, SamlAuth, UnhandledErrorForwarder, UrlPatternDiscovery, WebStorage, createFetchApi, createSpecializedApp, defaultConfigLoader };
2910
2995
  //# sourceMappingURL=index.esm.js.map