@backstage/core-app-api 1.3.1-next.0 → 1.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @backstage/core-app-api
2
2
 
3
+ ## 1.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bca8e8b393: Allow defining application level feature flags. See [Feature Flags documentation](https://backstage.io/docs/plugins/feature-flags#in-the-application) for reference.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @backstage/core-plugin-api@1.3.0
13
+ - @backstage/config@1.0.6
14
+ - @backstage/types@1.0.2
15
+ - @backstage/version-bridge@1.0.3
16
+
17
+ ## 1.4.0-next.1
18
+
19
+ ### Minor Changes
20
+
21
+ - bca8e8b393: Allow defining application level feature flags. See [Feature Flags documentation](https://backstage.io/docs/plugins/feature-flags#in-the-application) for reference.
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies
26
+ - @backstage/core-plugin-api@1.3.0-next.1
27
+ - @backstage/config@1.0.6-next.0
28
+ - @backstage/types@1.0.2
29
+ - @backstage/version-bridge@1.0.3
30
+
3
31
  ## 1.3.1-next.0
4
32
 
5
33
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -739,6 +739,10 @@ declare type AppOptions = {
739
739
  type: string;
740
740
  }>;
741
741
  }>;
742
+ /**
743
+ * Application level feature flags.
744
+ */
745
+ featureFlags?: (FeatureFlag & Omit<FeatureFlag, 'pluginId'>)[];
742
746
  /**
743
747
  * Supply components to the app to override the default ones.
744
748
  */
package/dist/index.esm.js CHANGED
@@ -49,6 +49,10 @@ class ApiResolver {
49
49
  this.factories = factories;
50
50
  this.apis = /* @__PURE__ */ new Map();
51
51
  }
52
+ /**
53
+ * Validate factories by making sure that each of the apis can be created
54
+ * without hitting any circular dependencies.
55
+ */
52
56
  static validateFactories(factories, apis) {
53
57
  for (const api of apis) {
54
58
  const heap = [api];
@@ -119,6 +123,13 @@ class ApiFactoryRegistry {
119
123
  constructor() {
120
124
  this.factories = /* @__PURE__ */ new Map();
121
125
  }
126
+ /**
127
+ * Register a new API factory. Returns true if the factory was added
128
+ * to the registry.
129
+ *
130
+ * A factory will not be added to the registry if there is already
131
+ * an existing factory with the same or higher priority.
132
+ */
122
133
  register(scope, factory) {
123
134
  const priority = ScopePriority[scope];
124
135
  const existing = this.factories.get(factory.api.id);
@@ -645,6 +656,12 @@ class StaticAuthSessionManager {
645
656
  this.stateTracker.setIsSignedIn(true);
646
657
  return this.currentSession;
647
658
  }
659
+ /**
660
+ * We don't call this.connector.removeSession here, since this session manager
661
+ * is intended to be static. As such there's no need to hit the remote logout
662
+ * endpoint - simply discarding the local session state when signing out is
663
+ * enough.
664
+ */
648
665
  async removeSession() {
649
666
  this.currentSession = void 0;
650
667
  this.stateTracker.setIsSignedIn(false);
@@ -1179,9 +1196,25 @@ class MultipleAnalyticsApi {
1179
1196
  constructor(actualApis) {
1180
1197
  this.actualApis = actualApis;
1181
1198
  }
1199
+ /**
1200
+ * Create an AnalyticsApi implementation from an array of concrete
1201
+ * implementations.
1202
+ *
1203
+ * @example
1204
+ *
1205
+ * ```jsx
1206
+ * MultipleAnalyticsApi.fromApis([
1207
+ * SomeAnalyticsApi.fromConfig(configApi),
1208
+ * new CustomAnalyticsApi(),
1209
+ * ]);
1210
+ * ```
1211
+ */
1182
1212
  static fromApis(actualApis) {
1183
1213
  return new MultipleAnalyticsApi(actualApis);
1184
1214
  }
1215
+ /**
1216
+ * Forward the event to all configured analytics API implementations.
1217
+ */
1185
1218
  captureEvent(event) {
1186
1219
  this.actualApis.forEach((analyticsApi) => {
1187
1220
  try {
@@ -1247,6 +1280,13 @@ class UrlPatternDiscovery {
1247
1280
  constructor(parts) {
1248
1281
  this.parts = parts;
1249
1282
  }
1283
+ /**
1284
+ * Creates a new UrlPatternDiscovery given a template. The the only
1285
+ * interpolation done for the template is to replace instances of `{{pluginId}}`
1286
+ * with the ID of the plugin being requested.
1287
+ *
1288
+ * Example pattern: `http://localhost:7007/api/{{ pluginId }}`
1289
+ */
1250
1290
  static compile(pattern) {
1251
1291
  const parts = pattern.split(/\{\{\s*pluginId\s*\}\}/);
1252
1292
  const urlStr = parts.join("pluginId");
@@ -1301,6 +1341,9 @@ class ErrorApiForwarder {
1301
1341
  }
1302
1342
 
1303
1343
  class UnhandledErrorForwarder {
1344
+ /**
1345
+ * Add event listener, such that unhandled errors can be forwarded using an given `ErrorApi` instance
1346
+ */
1304
1347
  static forward(errorApi, errorContext) {
1305
1348
  window.addEventListener(
1306
1349
  "unhandledrejection",
@@ -1483,9 +1526,38 @@ function isUrl(a) {
1483
1526
  }
1484
1527
 
1485
1528
  class FetchMiddlewares {
1529
+ /**
1530
+ * Handles translation from `plugin://` URLs to concrete http(s) URLs based on
1531
+ * the discovery API.
1532
+ *
1533
+ * @remarks
1534
+ *
1535
+ * If the request is for `plugin://catalog/entities?filter=x=y`, the discovery
1536
+ * API will be queried for `'catalog'`. If it returned
1537
+ * `https://backstage.example.net/api/catalog`, the resulting query would be
1538
+ * `https://backstage.example.net/api/catalog/entities?filter=x=y`.
1539
+ *
1540
+ * If the incoming URL protocol was not `plugin`, the request is just passed
1541
+ * through verbatim to the underlying implementation.
1542
+ */
1486
1543
  static resolvePluginProtocol(options) {
1487
1544
  return new PluginProtocolResolverFetchMiddleware(options.discoveryApi);
1488
1545
  }
1546
+ /**
1547
+ * Injects a Backstage token header when the user is signed in.
1548
+ *
1549
+ * @remarks
1550
+ *
1551
+ * Per default, an `Authorization: Bearer <token>` is generated. This can be
1552
+ * customized using the `header` option.
1553
+ *
1554
+ * The header injection only happens on allowlisted URLs. Per default, if the
1555
+ * `config` option is passed in, the `backend.baseUrl` is allowlisted, unless
1556
+ * the `urlPrefixAllowlist` or `allowUrl` options are passed in, in which case
1557
+ * they take precedence. If you pass in neither config nor an
1558
+ * allowlist/callback, the middleware will have no effect since effectively no
1559
+ * request will match the (nonexistent) rules.
1560
+ */
1489
1561
  static injectIdentityAuth(options) {
1490
1562
  return IdentityAuthInjectorFetchMiddleware.create(options);
1491
1563
  }
@@ -1589,6 +1661,7 @@ class OAuthRequestManager {
1589
1661
  return handler.request(scopes);
1590
1662
  };
1591
1663
  }
1664
+ // Converts the pending request and popup options into a popup request that we can forward to subscribers.
1592
1665
  makeAuthRequest(request, options) {
1593
1666
  const { scopes } = request;
1594
1667
  if (!scopes) {
@@ -1767,6 +1840,7 @@ function readBasePath(configApi) {
1767
1840
  let { pathname } = new URL(
1768
1841
  (_a = configApi.getOptionalString("app.baseUrl")) != null ? _a : "/",
1769
1842
  "http://sample.dev"
1843
+ // baseUrl can be specified as just a path
1770
1844
  );
1771
1845
  pathname = pathname.replace(/\/*$/, "");
1772
1846
  return pathname;
@@ -1931,6 +2005,7 @@ const MATCH_ALL_ROUTE = {
1931
2005
  caseSensitive: false,
1932
2006
  path: "*",
1933
2007
  element: "match-all",
2008
+ // These elements aren't used, so we add in a bit of debug information
1934
2009
  routeRefs: /* @__PURE__ */ new Set()
1935
2010
  };
1936
2011
  function stringifyNode(node) {
@@ -2416,6 +2491,7 @@ class AppIdentityProxy {
2416
2491
  this.resolveTarget = resolve;
2417
2492
  });
2418
2493
  }
2494
+ // This is called by the app manager once the sign-in page provides us with an implementation
2419
2495
  setTarget(identityApi, targetOptions) {
2420
2496
  this.target = identityApi;
2421
2497
  this.signOutTargetUrl = targetOptions.signOutTargetUrl;
@@ -2577,12 +2653,29 @@ class ApiRegistry {
2577
2653
  static builder() {
2578
2654
  return new ApiRegistryBuilder();
2579
2655
  }
2656
+ /**
2657
+ * Creates a new ApiRegistry with a list of API implementations.
2658
+ *
2659
+ * @param apis - A list of pairs mapping an ApiRef to its respective implementation
2660
+ */
2580
2661
  static from(apis) {
2581
2662
  return new ApiRegistry(new Map(apis.map(([api, impl]) => [api.id, impl])));
2582
2663
  }
2664
+ /**
2665
+ * Creates a new ApiRegistry with a single API implementation.
2666
+ *
2667
+ * @param api - ApiRef for the API to add
2668
+ * @param impl - Implementation of the API to add
2669
+ */
2583
2670
  static with(api, impl) {
2584
2671
  return new ApiRegistry(/* @__PURE__ */ new Map([[api.id, impl]]));
2585
2672
  }
2673
+ /**
2674
+ * Returns a new ApiRegistry with the provided API added to the existing ones.
2675
+ *
2676
+ * @param api - ApiRef for the API to add
2677
+ * @param impl - Implementation of the API to add
2678
+ */
2586
2679
  with(api, impl) {
2587
2680
  return new ApiRegistry(new Map([...this.apis, [api.id, impl]]));
2588
2681
  }
@@ -2715,14 +2808,15 @@ class AppManager {
2715
2808
  constructor(options) {
2716
2809
  this.appIdentityProxy = new AppIdentityProxy();
2717
2810
  __privateAdd(this, _getProviderCalled, false);
2718
- var _a, _b, _c, _d;
2811
+ var _a, _b, _c, _d, _e;
2719
2812
  this.apis = (_a = options.apis) != null ? _a : [];
2720
2813
  this.icons = options.icons;
2721
2814
  this.plugins = new Set((_b = options.plugins) != null ? _b : []);
2815
+ this.featureFlags = (_c = options.featureFlags) != null ? _c : [];
2722
2816
  this.components = options.components;
2723
2817
  this.themes = options.themes;
2724
- this.configLoader = (_c = options.configLoader) != null ? _c : defaultConfigLoader;
2725
- this.defaultApis = (_d = options.defaultApis) != null ? _d : [];
2818
+ this.configLoader = (_d = options.configLoader) != null ? _d : defaultConfigLoader;
2819
+ this.defaultApis = (_e = options.defaultApis) != null ? _e : [];
2726
2820
  this.bindRoutes = options.bindRoutes;
2727
2821
  this.apiFactoryRegistry = new ApiFactoryRegistry();
2728
2822
  }
@@ -2803,6 +2897,12 @@ class AppManager {
2803
2897
  needsFeatureFlagRegistrationRef.current = false;
2804
2898
  const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef);
2805
2899
  if (featureFlagsApi) {
2900
+ for (const flag of this.featureFlags) {
2901
+ featureFlagsApi.registerFlag({
2902
+ ...flag,
2903
+ pluginId: ""
2904
+ });
2905
+ }
2806
2906
  for (const plugin of this.plugins.values()) {
2807
2907
  if ("getFeatureFlags" in plugin) {
2808
2908
  for (const flag of plugin.getFeatureFlags()) {
@@ -2969,11 +3069,15 @@ const FlatRoutes = (props) => {
2969
3069
  }
2970
3070
  return [
2971
3071
  {
3072
+ // Each route matches any sub route, except for the explicit root path
2972
3073
  path,
2973
3074
  element,
2974
3075
  children: child.props.children ? [
3076
+ // These are the children of each route, which we all add in under a catch-all
3077
+ // subroute in order to make them available to `useOutlet`
2975
3078
  {
2976
3079
  path: path === "/" ? "/" : "*",
3080
+ // The root path must require an exact match
2977
3081
  element: child.props.children
2978
3082
  }
2979
3083
  ] : void 0