@backstage/core-app-api 1.3.0 → 1.4.0-next.1
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 +24 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +107 -3
- package/dist/index.esm.js.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @backstage/core-app-api
|
|
2
2
|
|
|
3
|
+
## 1.4.0-next.1
|
|
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-next.1
|
|
13
|
+
- @backstage/config@1.0.6-next.0
|
|
14
|
+
- @backstage/types@1.0.2
|
|
15
|
+
- @backstage/version-bridge@1.0.3
|
|
16
|
+
|
|
17
|
+
## 1.3.1-next.0
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
- @backstage/config@1.0.6-next.0
|
|
23
|
+
- @backstage/core-plugin-api@1.2.1-next.0
|
|
24
|
+
- @backstage/types@1.0.2
|
|
25
|
+
- @backstage/version-bridge@1.0.3
|
|
26
|
+
|
|
3
27
|
## 1.3.0
|
|
4
28
|
|
|
5
29
|
### Minor 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 = (
|
|
2725
|
-
this.defaultApis = (
|
|
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
|