@backstage/core-app-api 1.2.1-next.1 → 1.2.1-next.3

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,31 @@
1
1
  # @backstage/core-app-api
2
2
 
3
+ ## 1.2.1-next.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 6870b43dd1: Fix for the automatic rewriting of base URLs.
8
+ - 653d7912ac: Made `WebStorage` notify its subscribers when `localStorage` values change in other tabs/windows
9
+ - Updated dependencies
10
+ - @backstage/config@1.0.5-next.1
11
+ - @backstage/core-plugin-api@1.2.0-next.2
12
+ - @backstage/types@1.0.2-next.1
13
+ - @backstage/version-bridge@1.0.3-next.0
14
+
15
+ ## 1.2.1-next.2
16
+
17
+ ### Patch Changes
18
+
19
+ - b4b5b02315: Tweak feature flag registration so that it happens immediately before the first rendering of the app, rather than just after.
20
+ - 203271b746: Prevent duplicate feature flag components from rendering in the settings when using <FeatureFlagged /> components
21
+ - 8015ff1258: Tweaked wording to use inclusive terminology
22
+ - 63310e3987: Apps will now rewrite the `app.baseUrl` configuration to match the current `location.origin`. The `backend.baseUrl` will also be rewritten in the same way when the `app.baseUrl` and `backend.baseUrl` have matching origins. This will reduce the need for separate frontend builds for different environments.
23
+ - Updated dependencies
24
+ - @backstage/core-plugin-api@1.2.0-next.2
25
+ - @backstage/config@1.0.5-next.1
26
+ - @backstage/types@1.0.2-next.1
27
+ - @backstage/version-bridge@1.0.3-next.0
28
+
3
29
  ## 1.2.1-next.1
4
30
 
5
31
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -535,16 +535,19 @@ 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;
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useContext, Children, isValidElement, useEffect, useMemo, useState, createContext } from 'react';
1
+ import React, { useContext, Children, isValidElement, useEffect, useMemo, useState, createContext, 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';
@@ -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,7 +1703,9 @@ class WebStorage {
1680
1703
  subscription.next(snapshot);
1681
1704
  }
1682
1705
  }
1683
- }
1706
+ };
1707
+ let WebStorage = _WebStorage;
1708
+ WebStorage.hasSubscribed = false;
1684
1709
 
1685
1710
  function traverseElementTree(options) {
1686
1711
  const collectors = {};
@@ -2509,7 +2534,7 @@ function readBasePath(configApi) {
2509
2534
  var _a;
2510
2535
  let { pathname } = new URL(
2511
2536
  (_a = configApi.getOptionalString("app.baseUrl")) != null ? _a : "/",
2512
- "http://dummy.dev"
2537
+ "http://sample.dev"
2513
2538
  );
2514
2539
  pathname = pathname.replace(/\/*$/, "");
2515
2540
  return pathname;
@@ -2532,7 +2557,46 @@ function useConfigLoader(configLoader, components, appThemeApi) {
2532
2557
  node: /* @__PURE__ */ React.createElement(ApiProvider, { apis: ApiRegistry.with(appThemeApiRef, appThemeApi) }, /* @__PURE__ */ React.createElement(ThemeProvider, null, noConfigNode))
2533
2558
  };
2534
2559
  }
2535
- const configReader = ConfigReader.fromConfigs((_a = config.value) != null ? _a : []);
2560
+ let configReader;
2561
+ if ((_a = config.value) == null ? void 0 : _a.length) {
2562
+ const urlConfigReader = ConfigReader.fromConfigs(config.value);
2563
+ const getOrigin = (url) => new URL(url).origin;
2564
+ const overrideOrigin = (fullUrl) => {
2565
+ return new URL(
2566
+ fullUrl.replace(getOrigin(fullUrl), ""),
2567
+ document.location.origin
2568
+ ).href.replace(/\/$/, "");
2569
+ };
2570
+ const appBaseUrl = urlConfigReader.getOptionalString("app.baseUrl");
2571
+ const backendBaseUrl = urlConfigReader.getOptionalString("backend.baseUrl");
2572
+ let configs = config.value;
2573
+ const relativeResolverConfig = {
2574
+ data: {},
2575
+ context: "relative-resolver"
2576
+ };
2577
+ if (appBaseUrl && backendBaseUrl) {
2578
+ const appOrigin = getOrigin(appBaseUrl);
2579
+ const backendOrigin = getOrigin(backendBaseUrl);
2580
+ if (appOrigin === backendOrigin) {
2581
+ const newBackendBaseUrl = overrideOrigin(backendBaseUrl);
2582
+ if (backendBaseUrl !== newBackendBaseUrl) {
2583
+ relativeResolverConfig.data.backend = { baseUrl: newBackendBaseUrl };
2584
+ }
2585
+ }
2586
+ }
2587
+ if (appBaseUrl) {
2588
+ const newAppBaseUrl = overrideOrigin(appBaseUrl);
2589
+ if (appBaseUrl !== newAppBaseUrl) {
2590
+ relativeResolverConfig.data.app = { baseUrl: newAppBaseUrl };
2591
+ }
2592
+ }
2593
+ if (Object.keys(relativeResolverConfig.data).length) {
2594
+ configs = configs.concat([relativeResolverConfig]);
2595
+ }
2596
+ configReader = ConfigReader.fromConfigs(configs);
2597
+ } else {
2598
+ configReader = ConfigReader.fromConfigs([]);
2599
+ }
2536
2600
  return { api: configReader };
2537
2601
  }
2538
2602
  class AppContextImpl {
@@ -2582,6 +2646,7 @@ class AppManager {
2582
2646
  const appContext = new AppContextImpl(this);
2583
2647
  let routesHaveBeenValidated = false;
2584
2648
  const Provider = ({ children }) => {
2649
+ const needsFeatureFlagRegistrationRef = useRef(true);
2585
2650
  const appThemeApi = useMemo(
2586
2651
  () => AppThemeSelector.createWithStorage(this.themes),
2587
2652
  []
@@ -2622,9 +2687,13 @@ class AppManager {
2622
2687
  const { api } = loadedConfig;
2623
2688
  this.configApi = api;
2624
2689
  }
2625
- useEffect(() => {
2626
- if (hasConfigApi) {
2627
- const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef);
2690
+ if ("node" in loadedConfig) {
2691
+ return loadedConfig.node;
2692
+ }
2693
+ if (hasConfigApi && needsFeatureFlagRegistrationRef.current) {
2694
+ needsFeatureFlagRegistrationRef.current = false;
2695
+ const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef);
2696
+ if (featureFlagsApi) {
2628
2697
  for (const plugin of this.plugins.values()) {
2629
2698
  if ("getFeatureFlags" in plugin) {
2630
2699
  for (const flag of plugin.getFeatureFlags()) {
@@ -2644,13 +2713,14 @@ class AppManager {
2644
2713
  }
2645
2714
  }
2646
2715
  }
2716
+ const registeredFlags = featureFlagsApi.getRegisteredFlags();
2717
+ const flagNames = new Set(registeredFlags.map((f) => f.name));
2647
2718
  for (const name of featureFlags) {
2648
- featureFlagsApi.registerFlag({ name, pluginId: "" });
2719
+ if (!flagNames.has(name)) {
2720
+ featureFlagsApi.registerFlag({ name, pluginId: "" });
2721
+ }
2649
2722
  }
2650
2723
  }
2651
- }, [hasConfigApi, loadedConfig, featureFlags]);
2652
- if ("node" in loadedConfig) {
2653
- return loadedConfig.node;
2654
2724
  }
2655
2725
  const { ThemeProvider = AppThemeProvider } = this.components;
2656
2726
  return /* @__PURE__ */ React.createElement(ApiProvider, { apis: this.getApiHolder() }, /* @__PURE__ */ React.createElement(AppContextProvider, { appContext }, /* @__PURE__ */ React.createElement(ThemeProvider, null, /* @__PURE__ */ React.createElement(