@backstage/core-app-api 0.3.0 → 0.5.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,57 @@
1
1
  # @backstage/core-app-api
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ceebe25391: Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback.
8
+
9
+ ### Patch Changes
10
+
11
+ - fb565073ec: Add an `allowUrl` callback option to `FetchMiddlewares.injectIdentityAuth`
12
+ - f050eec2c0: Added validation during the application startup that detects if there are any plugins present that have not had their required external routes bound. Failing the validation will cause a hard crash as it is a programmer error. It lets you detect early on that there are dangling routes, rather than having them cause an error later on.
13
+ - Updated dependencies
14
+ - @backstage/core-plugin-api@0.6.0
15
+ - @backstage/config@0.1.13
16
+
17
+ ## 0.5.0-next.0
18
+
19
+ ### Minor Changes
20
+
21
+ - ceebe25391: Removed deprecated `SignInResult` type, which was replaced with the new `onSignInSuccess` callback.
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies
26
+ - @backstage/core-plugin-api@0.6.0-next.0
27
+ - @backstage/config@0.1.13-next.0
28
+
29
+ ## 0.4.0
30
+
31
+ ### Minor Changes
32
+
33
+ - e2eb92c109: Removed previously deprecated `ApiRegistry` export.
34
+
35
+ ### Patch Changes
36
+
37
+ - 34442cd5cf: Fixed an issue where valid SAML and GitHub sessions would be considered invalid and not be stored.
38
+
39
+ Deprecated the `SamlSession` and `GithubSession` types.
40
+
41
+ - 784d8078ab: Removed direct and transitive MUI dependencies.
42
+ - Updated dependencies
43
+ - @backstage/config@0.1.12
44
+ - @backstage/core-plugin-api@0.5.0
45
+
46
+ ## 0.3.1
47
+
48
+ ### Patch Changes
49
+
50
+ - 4ce51ab0f1: Internal refactor of the `react-use` imports to use `react-use/lib/*` instead.
51
+ - Updated dependencies
52
+ - @backstage/core-plugin-api@0.4.1
53
+ - @backstage/core-components@0.8.3
54
+
3
55
  ## 0.3.0
4
56
 
5
57
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReactNode, PropsWithChildren, ComponentType } from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { ApiHolder, ApiRef, ApiFactory, AnyApiRef, ProfileInfo, BackstageIdentity, OAuthRequestApi, DiscoveryApi, AuthProviderInfo, OAuthApi, SessionApi, SessionState, AuthRequestOptions, gitlabAuthApiRef, googleAuthApiRef, OpenIdConnectApi, ProfileInfoApi, BackstageIdentityApi, oktaAuthApiRef, auth0AuthApiRef, microsoftAuthApiRef, oneloginAuthApiRef, bitbucketAuthApiRef, atlassianAuthApiRef, AlertApi, AlertMessage, AnalyticsApi, AnalyticsEvent, AppThemeApi, AppTheme, ErrorApi, ErrorApiError, ErrorApiErrorContext, FeatureFlagsApi, FeatureFlag, FeatureFlagsSaveOptions, FetchApi, IdentityApi, OAuthRequesterOptions, OAuthRequester, PendingOAuthRequest, StorageApi, StorageValueSnapshot, BackstagePlugin, IconComponent, ExternalRouteRef, AnyApiFactory, PluginOutput, RouteRef, SubRouteRef } from '@backstage/core-plugin-api';
3
+ import { ApiHolder, ApiRef, ApiFactory, AnyApiRef, ProfileInfo, BackstageIdentityResponse, OAuthRequestApi, DiscoveryApi, AuthProviderInfo, OAuthApi, SessionApi, SessionState, AuthRequestOptions, gitlabAuthApiRef, googleAuthApiRef, OpenIdConnectApi, ProfileInfoApi, BackstageIdentityApi, oktaAuthApiRef, auth0AuthApiRef, microsoftAuthApiRef, oneloginAuthApiRef, bitbucketAuthApiRef, atlassianAuthApiRef, AlertApi, AlertMessage, AnalyticsApi, AnalyticsEvent, AppThemeApi, AppTheme, ErrorApi, ErrorApiError, ErrorApiErrorContext, FeatureFlagsApi, FeatureFlag, FeatureFlagsSaveOptions, FetchApi, IdentityApi, OAuthRequesterOptions, OAuthRequester, PendingOAuthRequest, StorageApi, StorageValueSnapshot, BackstagePlugin, IconComponent, ExternalRouteRef, AnyApiFactory, RouteRef, SubRouteRef } from '@backstage/core-plugin-api';
4
4
  import * as _backstage_types from '@backstage/types';
5
5
  import { Observable, JsonValue } from '@backstage/types';
6
6
  import { Config, AppConfig } from '@backstage/config';
@@ -30,45 +30,6 @@ declare const ApiProvider: {
30
30
  };
31
31
  };
32
32
 
33
- declare type ApiImpl<T = unknown> = readonly [ApiRef<T>, T];
34
- declare class ApiRegistryBuilder {
35
- private apis;
36
- add<T, I extends T>(api: ApiRef<T>, impl: I): I;
37
- build(): ApiRegistry;
38
- }
39
- /**
40
- * A registry for utility APIs.
41
- *
42
- * @public
43
- * @deprecated Will be removed, use {@link @backstage/test-utils#TestApiProvider} or {@link @backstage/test-utils#TestApiRegistry} instead.
44
- */
45
- declare class ApiRegistry implements ApiHolder {
46
- private readonly apis;
47
- static builder(): ApiRegistryBuilder;
48
- /**
49
- * Creates a new ApiRegistry with a list of API implementations.
50
- *
51
- * @param apis - A list of pairs mapping an ApiRef to its respective implementation
52
- */
53
- static from(apis: ApiImpl[]): ApiRegistry;
54
- /**
55
- * Creates a new ApiRegistry with a single API implementation.
56
- *
57
- * @param api - ApiRef for the API to add
58
- * @param impl - Implementation of the API to add
59
- */
60
- static with<T>(api: ApiRef<T>, impl: T): ApiRegistry;
61
- constructor(apis: Map<string, unknown>);
62
- /**
63
- * Returns a new ApiRegistry with the provided API added to the existing ones.
64
- *
65
- * @param api - ApiRef for the API to add
66
- * @param impl - Implementation of the API to add
67
- */
68
- with<T>(api: ApiRef<T>, impl: T): ApiRegistry;
69
- get<T>(api: ApiRef<T>): T | undefined;
70
- }
71
-
72
33
  /**
73
34
  * @public
74
35
  */
@@ -134,6 +95,7 @@ declare class ApiFactoryRegistry implements ApiFactoryHolder {
134
95
  * Session information for GitHub auth.
135
96
  *
136
97
  * @public
98
+ * @deprecated This type is internal and will be removed
137
99
  */
138
100
  declare type GithubSession = {
139
101
  providerInfo: {
@@ -142,7 +104,7 @@ declare type GithubSession = {
142
104
  expiresAt?: Date;
143
105
  };
144
106
  profile: ProfileInfo;
145
- backstageIdentity: BackstageIdentity;
107
+ backstageIdentity: BackstageIdentityResponse;
146
108
  };
147
109
 
148
110
  /**
@@ -176,7 +138,7 @@ declare class GithubAuth implements OAuthApi, SessionApi {
176
138
  signOut(): Promise<void>;
177
139
  sessionState$(): Observable<SessionState>;
178
140
  getAccessToken(scope?: string, options?: AuthRequestOptions): Promise<string>;
179
- getBackstageIdentity(options?: AuthRequestOptions): Promise<BackstageIdentity | undefined>;
141
+ getBackstageIdentity(options?: AuthRequestOptions): Promise<BackstageIdentityResponse | undefined>;
180
142
  getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>;
181
143
  static normalizeScope(scope?: string): Set<string>;
182
144
  }
@@ -221,7 +183,7 @@ declare class OAuth2 implements OAuthApi, OpenIdConnectApi, ProfileInfoApi, Back
221
183
  sessionState$(): Observable<SessionState>;
222
184
  getAccessToken(scope?: string | string[], options?: AuthRequestOptions): Promise<string>;
223
185
  getIdToken(options?: AuthRequestOptions): Promise<string>;
224
- getBackstageIdentity(options?: AuthRequestOptions): Promise<BackstageIdentity | undefined>;
186
+ getBackstageIdentity(options?: AuthRequestOptions): Promise<BackstageIdentityResponse | undefined>;
225
187
  getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>;
226
188
  private static normalizeScopes;
227
189
  }
@@ -239,7 +201,7 @@ declare type OAuth2Session = {
239
201
  expiresAt: Date;
240
202
  };
241
203
  profile: ProfileInfo;
242
- backstageIdentity: BackstageIdentity;
204
+ backstageIdentity: BackstageIdentityResponse;
243
205
  };
244
206
 
245
207
  /**
@@ -263,7 +225,7 @@ declare class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionA
263
225
  private constructor();
264
226
  signIn(): Promise<void>;
265
227
  signOut(): Promise<void>;
266
- getBackstageIdentity(options?: AuthRequestOptions): Promise<BackstageIdentity | undefined>;
228
+ getBackstageIdentity(options?: AuthRequestOptions): Promise<BackstageIdentityResponse | undefined>;
267
229
  getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>;
268
230
  }
269
231
 
@@ -271,11 +233,12 @@ declare class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionA
271
233
  * Session information for SAML auth.
272
234
  *
273
235
  * @public
236
+ * @deprecated This type is internal and will be removed
274
237
  */
275
- declare type SamlSession = {
238
+ declare type ExportedSamlSession = {
276
239
  userId: string;
277
240
  profile: ProfileInfo;
278
- backstageIdentity: BackstageIdentity;
241
+ backstageIdentity: BackstageIdentityResponse;
279
242
  };
280
243
 
281
244
  /**
@@ -344,7 +307,7 @@ declare type BitbucketSession = {
344
307
  expiresAt?: Date;
345
308
  };
346
309
  profile: ProfileInfo;
347
- backstageIdentity: BackstageIdentity;
310
+ backstageIdentity: BackstageIdentityResponse;
348
311
  };
349
312
 
350
313
  /**
@@ -547,14 +510,16 @@ declare class FetchMiddlewares {
547
510
  *
548
511
  * The header injection only happens on allowlisted URLs. Per default, if the
549
512
  * `config` option is passed in, the `backend.baseUrl` is allowlisted, unless
550
- * the `urlPrefixAllowlist` option is passed in, in which case it takes
551
- * precedence. If you pass in neither config nor an allowlist, the middleware
552
- * will have no effect.
513
+ * the `urlPrefixAllowlist` or `allowUrl` options are passed in, in which case
514
+ * they take precedence. If you pass in neither config nor an
515
+ * allowlist/callback, the middleware will have no effect since effectively no
516
+ * request will match the (nonexistent) rules.
553
517
  */
554
518
  static injectIdentityAuth(options: {
555
519
  identityApi: IdentityApi;
556
520
  config?: Config;
557
521
  urlPrefixAllowlist?: string[];
522
+ allowUrl?: (url: string) => boolean;
558
523
  header?: {
559
524
  name: string;
560
525
  value: (backstageToken: string) => string;
@@ -615,27 +580,6 @@ declare type BootErrorPageProps = {
615
580
  step: 'load-config' | 'load-chunk';
616
581
  error: Error;
617
582
  };
618
- /**
619
- * The outcome of signing in on the sign-in page.
620
- *
621
- * @public
622
- * @deprecated replaced by passing the {@link @backstage/core-plugin-api#IdentityApi} to the {@link SignInPageProps.onSignInSuccess} instead.
623
- */
624
- declare type SignInResult = {
625
- /**
626
- * User ID that will be returned by the IdentityApi
627
- */
628
- userId: string;
629
- profile: ProfileInfo;
630
- /**
631
- * Function used to retrieve an ID token for the signed in user.
632
- */
633
- getIdToken?: () => Promise<string>;
634
- /**
635
- * Sign out handler that will be called if the user requests to sign out.
636
- */
637
- signOut?: () => Promise<void>;
638
- };
639
583
  /**
640
584
  * Props for the `SignInPage` component of {@link AppComponents}.
641
585
  *
@@ -782,11 +726,14 @@ declare type AppOptions = {
782
726
  /**
783
727
  * A list of all plugins to include in the app.
784
728
  */
785
- plugins?: (Omit<BackstagePlugin<any, any>, 'output'> & {
786
- output(): (PluginOutput | {
729
+ plugins?: Array<BackstagePlugin<any, any> & {
730
+ output?(): Array<{
731
+ type: 'feature-flag';
732
+ name: string;
733
+ } | {
787
734
  type: string;
788
- })[];
789
- })[];
735
+ }>;
736
+ }>;
790
737
  /**
791
738
  * Supply components to the app to override the default ones.
792
739
  */
@@ -971,4 +918,4 @@ declare type FeatureFlaggedProps = {
971
918
  */
972
919
  declare const FeatureFlagged: (props: FeatureFlaggedProps) => JSX.Element;
973
920
 
974
- export { AlertApiForwarder, ApiFactoryHolder, ApiFactoryRegistry, ApiFactoryScope, ApiProvider, ApiProviderProps, ApiRegistry, ApiResolver, AppComponents, AppConfigLoader, AppContext, AppIcons, AppOptions, AppRouteBinder, AppThemeSelector, AtlassianAuth, Auth0Auth, AuthApiCreateOptions, BackstageApp, BitbucketAuth, BitbucketSession, BootErrorPageProps, ErrorAlerter, ErrorApiForwarder, ErrorBoundaryFallbackProps, FeatureFlagged, FeatureFlaggedProps, FetchMiddleware, FetchMiddlewares, FlatRoutes, FlatRoutesProps, GithubAuth, GithubSession, GitlabAuth, GoogleAuth, LocalStorageFeatureFlags, MicrosoftAuth, NoOpAnalyticsApi, OAuth2, OAuth2CreateOptions, OAuth2Session, OAuthApiCreateOptions, OAuthRequestManager, OktaAuth, OneLoginAuth, OneLoginAuthCreateOptions, SamlAuth, SamlSession, SignInPageProps, SignInResult, UnhandledErrorForwarder, UrlPatternDiscovery, WebStorage, createFetchApi, createSpecializedApp, defaultConfigLoader };
921
+ export { AlertApiForwarder, ApiFactoryHolder, ApiFactoryRegistry, ApiFactoryScope, ApiProvider, ApiProviderProps, ApiResolver, AppComponents, AppConfigLoader, AppContext, AppIcons, AppOptions, AppRouteBinder, AppThemeSelector, AtlassianAuth, Auth0Auth, AuthApiCreateOptions, BackstageApp, BitbucketAuth, BitbucketSession, BootErrorPageProps, ErrorAlerter, ErrorApiForwarder, ErrorBoundaryFallbackProps, FeatureFlagged, FeatureFlaggedProps, FetchMiddleware, FetchMiddlewares, FlatRoutes, FlatRoutesProps, GithubAuth, GithubSession, GitlabAuth, GoogleAuth, LocalStorageFeatureFlags, MicrosoftAuth, NoOpAnalyticsApi, OAuth2, OAuth2CreateOptions, OAuth2Session, OAuthApiCreateOptions, OAuthRequestManager, OktaAuth, OneLoginAuth, OneLoginAuthCreateOptions, SamlAuth, ExportedSamlSession as SamlSession, SignInPageProps, UnhandledErrorForwarder, UrlPatternDiscovery, WebStorage, createFetchApi, createSpecializedApp, defaultConfigLoader };
package/dist/index.esm.js CHANGED
@@ -7,8 +7,8 @@ import { z } from 'zod';
7
7
  import { ConfigReader } from '@backstage/config';
8
8
  export { ConfigReader } from '@backstage/config';
9
9
  import { matchRoutes, generatePath, useLocation, Routes, Route, useRoutes } from 'react-router-dom';
10
- import { useObservable, useAsync } from 'react-use';
11
- import { UserIdentity } from '@backstage/core-components';
10
+ import useAsync from 'react-use/lib/useAsync';
11
+ import useObservable from 'react-use/lib/useObservable';
12
12
 
13
13
  class ApiAggregator {
14
14
  constructor(...holders) {
@@ -41,39 +41,6 @@ ApiProvider.propTypes = {
41
41
  children: PropTypes.node
42
42
  };
43
43
 
44
- class ApiRegistryBuilder {
45
- constructor() {
46
- this.apis = [];
47
- }
48
- add(api, impl) {
49
- this.apis.push([api.id, impl]);
50
- return impl;
51
- }
52
- build() {
53
- return new ApiRegistry(new Map(this.apis));
54
- }
55
- }
56
- class ApiRegistry {
57
- constructor(apis) {
58
- this.apis = apis;
59
- }
60
- static builder() {
61
- return new ApiRegistryBuilder();
62
- }
63
- static from(apis) {
64
- return new ApiRegistry(new Map(apis.map(([api, impl]) => [api.id, impl])));
65
- }
66
- static with(api, impl) {
67
- return new ApiRegistry(/* @__PURE__ */ new Map([[api.id, impl]]));
68
- }
69
- with(api, impl) {
70
- return new ApiRegistry(new Map([...this.apis, [api.id, impl]]));
71
- }
72
- get(api) {
73
- return this.apis.get(api.id);
74
- }
75
- }
76
-
77
44
  class ApiResolver {
78
45
  constructor(factories) {
79
46
  this.factories = factories;
@@ -1118,7 +1085,6 @@ class OktaAuth {
1118
1085
  }
1119
1086
 
1120
1087
  const samlSessionSchema = z.object({
1121
- userId: z.string(),
1122
1088
  profile: z.object({
1123
1089
  email: z.string().optional(),
1124
1090
  displayName: z.string().optional(),
@@ -1531,29 +1497,24 @@ function createFetchApi(options) {
1531
1497
  }
1532
1498
 
1533
1499
  class IdentityAuthInjectorFetchMiddleware {
1534
- constructor(identityApi, urlPrefixAllowlist, headerName, headerValue) {
1500
+ constructor(identityApi, allowUrl, headerName, headerValue) {
1535
1501
  this.identityApi = identityApi;
1536
- this.urlPrefixAllowlist = urlPrefixAllowlist;
1502
+ this.allowUrl = allowUrl;
1537
1503
  this.headerName = headerName;
1538
1504
  this.headerValue = headerValue;
1539
1505
  }
1540
1506
  static create(options) {
1541
1507
  var _a, _b;
1542
- const allowlist = [];
1543
- if (options.urlPrefixAllowlist) {
1544
- allowlist.push(...options.urlPrefixAllowlist);
1545
- } else if (options.config) {
1546
- allowlist.push(options.config.getString("backend.baseUrl"));
1547
- }
1508
+ const matcher = buildMatcher(options);
1548
1509
  const headerName = ((_a = options.header) == null ? void 0 : _a.name) || "authorization";
1549
1510
  const headerValue = ((_b = options.header) == null ? void 0 : _b.value) || ((token) => `Bearer ${token}`);
1550
- return new IdentityAuthInjectorFetchMiddleware(options.identityApi, allowlist.map((prefix) => prefix.replace(/\/$/, "")), headerName, headerValue);
1511
+ return new IdentityAuthInjectorFetchMiddleware(options.identityApi, matcher, headerName, headerValue);
1551
1512
  }
1552
1513
  apply(next) {
1553
1514
  return async (input, init) => {
1554
1515
  const request = new Request(input, init);
1555
1516
  const { token } = await this.identityApi.getCredentials();
1556
- if (request.headers.get(this.headerName) || !this.urlPrefixAllowlist.some((prefix) => request.url === prefix || request.url.startsWith(`${prefix}/`)) || typeof token !== "string" || !token) {
1517
+ if (request.headers.get(this.headerName) || typeof token !== "string" || !token || !this.allowUrl(request.url)) {
1557
1518
  return next(input, init);
1558
1519
  }
1559
1520
  request.headers.set(this.headerName, this.headerValue(token));
@@ -1561,6 +1522,20 @@ class IdentityAuthInjectorFetchMiddleware {
1561
1522
  };
1562
1523
  }
1563
1524
  }
1525
+ function buildMatcher(options) {
1526
+ if (options.allowUrl) {
1527
+ return options.allowUrl;
1528
+ } else if (options.urlPrefixAllowlist) {
1529
+ return buildPrefixMatcher(options.urlPrefixAllowlist);
1530
+ } else if (options.config) {
1531
+ return buildPrefixMatcher([options.config.getString("backend.baseUrl")]);
1532
+ }
1533
+ return () => false;
1534
+ }
1535
+ function buildPrefixMatcher(prefixes) {
1536
+ const trimmedPrefixes = prefixes.map((prefix) => prefix.replace(/\/$/, ""));
1537
+ return (url) => trimmedPrefixes.some((prefix) => url === prefix || url.startsWith(`${prefix}/`));
1538
+ }
1564
1539
 
1565
1540
  function join(left, right) {
1566
1541
  if (!right || right === "/") {
@@ -2172,7 +2147,7 @@ const RouteTracker = ({ tree }) => {
2172
2147
  }));
2173
2148
  };
2174
2149
 
2175
- function validateRoutes(routePaths, routeParents) {
2150
+ function validateRouteParameters(routePaths, routeParents) {
2176
2151
  const notLeafRoutes = new Set(routeParents.values());
2177
2152
  notLeafRoutes.delete(void 0);
2178
2153
  for (const route of routeParents.keys()) {
@@ -2201,6 +2176,21 @@ function validateRoutes(routePaths, routeParents) {
2201
2176
  }
2202
2177
  }
2203
2178
  }
2179
+ function validateRouteBindings(routeBindings, plugins) {
2180
+ for (const plugin of plugins) {
2181
+ if (!plugin.externalRoutes) {
2182
+ continue;
2183
+ }
2184
+ for (const [name, externalRouteRef] of Object.entries(plugin.externalRoutes)) {
2185
+ if (externalRouteRef.optional) {
2186
+ continue;
2187
+ }
2188
+ if (!routeBindings.has(externalRouteRef)) {
2189
+ throw new Error(`External route '${name}' of the '${plugin.getId()}' plugin must be bound to a target route. See https://backstage.io/link?bind-routes for details.`);
2190
+ }
2191
+ }
2192
+ }
2193
+ }
2204
2194
 
2205
2195
  const AppContext = createVersionedContext("app-context");
2206
2196
  const AppContextProvider = ({
@@ -2217,6 +2207,9 @@ const AppContextProvider = ({
2217
2207
  function mkError(thing) {
2218
2208
  return new Error(`Tried to access IdentityApi ${thing} before app was loaded`);
2219
2209
  }
2210
+ function logDeprecation(thing) {
2211
+ console.warn(`WARNING: Call to ${thing} is deprecated and will break in the future`);
2212
+ }
2220
2213
  class AppIdentityProxy {
2221
2214
  setTarget(identityApi) {
2222
2215
  this.target = identityApi;
@@ -2225,12 +2218,20 @@ class AppIdentityProxy {
2225
2218
  if (!this.target) {
2226
2219
  throw mkError("getUserId");
2227
2220
  }
2221
+ if (!this.target.getUserId) {
2222
+ throw new Error("IdentityApi does not implement getUserId");
2223
+ }
2224
+ logDeprecation("getUserId");
2228
2225
  return this.target.getUserId();
2229
2226
  }
2230
2227
  getProfile() {
2231
2228
  if (!this.target) {
2232
2229
  throw mkError("getProfile");
2233
2230
  }
2231
+ if (!this.target.getProfile) {
2232
+ throw new Error("IdentityApi does not implement getProfile");
2233
+ }
2234
+ logDeprecation("getProfile");
2234
2235
  return this.target.getProfile();
2235
2236
  }
2236
2237
  async getProfileInfo() {
@@ -2243,7 +2244,11 @@ class AppIdentityProxy {
2243
2244
  if (!this.target) {
2244
2245
  throw mkError("getBackstageIdentity");
2245
2246
  }
2246
- return this.target.getBackstageIdentity();
2247
+ const identity = await this.target.getBackstageIdentity();
2248
+ if (!identity.userEntityRef.match(/^.*:.*\/.*$/)) {
2249
+ console.warn(`WARNING: The App IdentityApi provided an invalid userEntityRef, '${identity.userEntityRef}'. It must be a full Entity Reference of the form '<kind>:<namespace>/<name>'.`);
2250
+ }
2251
+ return identity;
2247
2252
  }
2248
2253
  async getCredentials() {
2249
2254
  if (!this.target) {
@@ -2255,6 +2260,10 @@ class AppIdentityProxy {
2255
2260
  if (!this.target) {
2256
2261
  throw mkError("getIdToken");
2257
2262
  }
2263
+ if (!this.target.getIdToken) {
2264
+ throw new Error("IdentityApi does not implement getIdToken");
2265
+ }
2266
+ logDeprecation("getIdToken");
2258
2267
  return this.target.getIdToken();
2259
2268
  }
2260
2269
  async signOut() {
@@ -2343,7 +2352,40 @@ const defaultConfigLoader = async (runtimeConfigJson = "__APP_INJECTED_RUNTIME_C
2343
2352
  return configs;
2344
2353
  };
2345
2354
 
2346
- function generateBoundRoutes(bindRoutes) {
2355
+ class ApiRegistryBuilder {
2356
+ constructor() {
2357
+ this.apis = [];
2358
+ }
2359
+ add(api, impl) {
2360
+ this.apis.push([api.id, impl]);
2361
+ return impl;
2362
+ }
2363
+ build() {
2364
+ return new ApiRegistry(new Map(this.apis));
2365
+ }
2366
+ }
2367
+ class ApiRegistry {
2368
+ constructor(apis) {
2369
+ this.apis = apis;
2370
+ }
2371
+ static builder() {
2372
+ return new ApiRegistryBuilder();
2373
+ }
2374
+ static from(apis) {
2375
+ return new ApiRegistry(new Map(apis.map(([api, impl]) => [api.id, impl])));
2376
+ }
2377
+ static with(api, impl) {
2378
+ return new ApiRegistry(/* @__PURE__ */ new Map([[api.id, impl]]));
2379
+ }
2380
+ with(api, impl) {
2381
+ return new ApiRegistry(new Map([...this.apis, [api.id, impl]]));
2382
+ }
2383
+ get(api) {
2384
+ return this.apis.get(api.id);
2385
+ }
2386
+ }
2387
+
2388
+ function resolveRouteBindings(bindRoutes) {
2347
2389
  const result = /* @__PURE__ */ new Map();
2348
2390
  if (bindRoutes) {
2349
2391
  const bind = (externalRoutes, targetRoutes) => {
@@ -2364,6 +2406,7 @@ function generateBoundRoutes(bindRoutes) {
2364
2406
  }
2365
2407
  return result;
2366
2408
  }
2409
+
2367
2410
  function getBasePath(configApi) {
2368
2411
  var _a;
2369
2412
  let { pathname } = new URL((_a = configApi.getOptionalString("app.baseUrl")) != null ? _a : "/", "http://dummy.dev");
@@ -2389,7 +2432,7 @@ function useConfigLoader(configLoader, components, appThemeApi) {
2389
2432
  if (noConfigNode) {
2390
2433
  return {
2391
2434
  node: /* @__PURE__ */ React.createElement(ApiProvider, {
2392
- apis: ApiRegistry.from([[appThemeApiRef, appThemeApi]])
2435
+ apis: ApiRegistry.with(appThemeApiRef, appThemeApi)
2393
2436
  }, /* @__PURE__ */ React.createElement(ThemeProvider, null, noConfigNode))
2394
2437
  };
2395
2438
  }
@@ -2435,9 +2478,16 @@ class AppManager {
2435
2478
  }
2436
2479
  getProvider() {
2437
2480
  const appContext = new AppContextImpl(this);
2481
+ let routesHaveBeenValidated = false;
2438
2482
  const Provider = ({ children }) => {
2439
2483
  const appThemeApi = useMemo(() => AppThemeSelector.createWithStorage(this.themes), []);
2440
- const { routePaths, routeParents, routeObjects, featureFlags } = useMemo(() => {
2484
+ const {
2485
+ routePaths,
2486
+ routeParents,
2487
+ routeObjects,
2488
+ featureFlags,
2489
+ routeBindings
2490
+ } = useMemo(() => {
2441
2491
  const result = traverseElementTree({
2442
2492
  root: children,
2443
2493
  discoverers: [childDiscoverer, routeElementDiscoverer],
@@ -2449,12 +2499,19 @@ class AppManager {
2449
2499
  featureFlags: featureFlagCollector
2450
2500
  }
2451
2501
  });
2452
- validateRoutes(result.routePaths, result.routeParents);
2453
2502
  result.collectedPlugins.forEach((plugin) => this.plugins.add(plugin));
2454
2503
  this.verifyPlugins(this.plugins);
2455
2504
  this.getApiHolder();
2456
- return result;
2505
+ return {
2506
+ ...result,
2507
+ routeBindings: resolveRouteBindings(this.bindRoutes)
2508
+ };
2457
2509
  }, [children]);
2510
+ if (!routesHaveBeenValidated) {
2511
+ routesHaveBeenValidated = true;
2512
+ validateRouteParameters(routePaths, routeParents);
2513
+ validateRouteBindings(routeBindings, this.plugins);
2514
+ }
2458
2515
  const loadedConfig = useConfigLoader(this.configLoader, this.components, appThemeApi);
2459
2516
  const hasConfigApi = "api" in loadedConfig;
2460
2517
  if (hasConfigApi) {
@@ -2474,14 +2531,11 @@ class AppManager {
2474
2531
  }
2475
2532
  } else {
2476
2533
  for (const output of plugin.output()) {
2477
- switch (output.type) {
2478
- case "feature-flag": {
2479
- featureFlagsApi.registerFlag({
2480
- name: output.name,
2481
- pluginId: plugin.getId()
2482
- });
2483
- break;
2484
- }
2534
+ if (output.type === "feature-flag") {
2535
+ featureFlagsApi.registerFlag({
2536
+ name: output.name,
2537
+ pluginId: plugin.getId()
2538
+ });
2485
2539
  }
2486
2540
  }
2487
2541
  }
@@ -2503,7 +2557,7 @@ class AppManager {
2503
2557
  routePaths,
2504
2558
  routeParents,
2505
2559
  routeObjects,
2506
- routeBindings: generateBoundRoutes(this.bindRoutes),
2560
+ routeBindings,
2507
2561
  basePath: getBasePath(loadedConfig.api)
2508
2562
  }, children))));
2509
2563
  };
@@ -2528,7 +2582,26 @@ class AppManager {
2528
2582
  const configApi = useApi(configApiRef);
2529
2583
  const mountPath = `${getBasePath(configApi)}/*`;
2530
2584
  if (!SignInPageComponent) {
2531
- this.appIdentityProxy.setTarget(UserIdentity.createGuest());
2585
+ this.appIdentityProxy.setTarget({
2586
+ getUserId: () => "guest",
2587
+ getIdToken: async () => void 0,
2588
+ getProfile: () => ({
2589
+ email: "guest@example.com",
2590
+ displayName: "Guest"
2591
+ }),
2592
+ getProfileInfo: async () => ({
2593
+ email: "guest@example.com",
2594
+ displayName: "Guest"
2595
+ }),
2596
+ getBackstageIdentity: async () => ({
2597
+ type: "user",
2598
+ userEntityRef: "user:default/guest",
2599
+ ownershipEntityRefs: ["user:default/guest"]
2600
+ }),
2601
+ getCredentials: async () => ({}),
2602
+ signOut: async () => {
2603
+ }
2604
+ });
2532
2605
  return /* @__PURE__ */ React.createElement(RouterComponent, null, /* @__PURE__ */ React.createElement(RouteTracker, {
2533
2606
  tree: children
2534
2607
  }), /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, {
@@ -2652,5 +2725,5 @@ const FlatRoutes = (props) => {
2652
2725
  return useRoutes(routes);
2653
2726
  };
2654
2727
 
2655
- export { AlertApiForwarder, ApiFactoryRegistry, ApiProvider, ApiRegistry, ApiResolver, AppThemeSelector, AtlassianAuth, Auth0Auth, BitbucketAuth, ErrorAlerter, ErrorApiForwarder, FeatureFlagged, FetchMiddlewares, FlatRoutes, GithubAuth, GitlabAuth, GoogleAuth, LocalStorageFeatureFlags, MicrosoftAuth, NoOpAnalyticsApi, OAuth2, OAuthRequestManager, OktaAuth, OneLoginAuth, SamlAuth, UnhandledErrorForwarder, UrlPatternDiscovery, WebStorage, createFetchApi, createSpecializedApp, defaultConfigLoader };
2728
+ export { AlertApiForwarder, ApiFactoryRegistry, ApiProvider, ApiResolver, AppThemeSelector, AtlassianAuth, Auth0Auth, BitbucketAuth, ErrorAlerter, ErrorApiForwarder, FeatureFlagged, FetchMiddlewares, FlatRoutes, GithubAuth, GitlabAuth, GoogleAuth, LocalStorageFeatureFlags, MicrosoftAuth, NoOpAnalyticsApi, OAuth2, OAuthRequestManager, OktaAuth, OneLoginAuth, SamlAuth, UnhandledErrorForwarder, UrlPatternDiscovery, WebStorage, createFetchApi, createSpecializedApp, defaultConfigLoader };
2656
2729
  //# sourceMappingURL=index.esm.js.map