diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts index 5677299c83..cf69ead361 100644 --- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts @@ -46,11 +46,20 @@ type CompatibilityIdentityApi = IdentityApi & { * and sign-in page. */ export class AppIdentityProxy implements IdentityApi { - private target?: CompatibilityIdentityApi; +private target?: CompatibilityIdentityApi; +private waitForTarget: Promise; + private resolveTarget: (api: IdentityApi) => void = () => {}; + + constructor() { + this.waitForTarget = new Promise(resolve => { + this.resolveTarget = resolve; + }); + } // This is called by the app manager once the sign-in page provides us with an implementation setTarget(identityApi: CompatibilityIdentityApi) { this.target = identityApi; + this.resolveTarget(identityApi); } getUserId(): string { @@ -76,17 +85,11 @@ export class AppIdentityProxy implements IdentityApi { } async getProfileInfo(): Promise { - if (!this.target) { - throw mkError('getProfileInfo'); - } - return this.target.getProfileInfo(); + return this.waitForTarget.then(target => target.getProfileInfo()); } async getBackstageIdentity(): Promise { - if (!this.target) { - throw mkError('getBackstageIdentity'); - } - const identity = await this.target.getBackstageIdentity(); + const identity = await this.waitForTarget.then(target => target.getBackstageIdentity()); if (!identity.userEntityRef.match(/^.*:.*\/.*$/)) { // eslint-disable-next-line no-console console.warn( @@ -99,28 +102,21 @@ export class AppIdentityProxy implements IdentityApi { } async getCredentials(): Promise<{ token?: string | undefined }> { - if (!this.target) { - throw mkError('getCredentials'); - } - return this.target.getCredentials(); + return this.waitForTarget.then(target => target.getCredentials()); } async getIdToken(): Promise { - if (!this.target) { - throw mkError('getIdToken'); - } - if (!this.target.getIdToken) { - throw new Error('IdentityApi does not implement getIdToken'); - } - logDeprecation('getIdToken'); - return this.target.getIdToken(); + return this.waitForTarget.then((target: CompatibilityIdentityApi) => { + if (!target.getIdToken) { + throw new Error('IdentityApi does not implement getIdToken'); + } + logDeprecation('getIdToken'); + return target.getIdToken() + }); } async signOut(): Promise { - if (!this.target) { - throw mkError('signOut'); - } - await this.target.signOut(); + await this.waitForTarget.then(target => target.signOut()); location.reload(); } } diff --git a/packages/core-app-api/src/apis/system/ApiProvider.tsx b/packages/core-app-api/src/apis/system/ApiProvider.tsx index c75f883a51..681c741afb 100644 --- a/packages/core-app-api/src/apis/system/ApiProvider.tsx +++ b/packages/core-app-api/src/apis/system/ApiProvider.tsx @@ -53,6 +53,24 @@ export const ApiProvider = (props: PropsWithChildren) => { ); }; +/** + * Same as above, but does not inherit APIs from API providers further up in + * the react tree. + * + * Private to (and only used in) this package. + */ +export const IsolatedApiProvider = ( + props: PropsWithChildren, +) => { + const { apis, children } = props; + return ( + + ); +}; + ApiProvider.propTypes = { apis: PropTypes.shape({ get: PropTypes.func.isRequired }).isRequired, children: PropTypes.node, diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 0262ece8f4..484a63396d 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -79,6 +79,7 @@ import { AppThemeProvider } from './AppThemeProvider'; import { defaultConfigLoader } from './defaultConfigLoader'; import { ApiRegistry } from '../apis/system/ApiRegistry'; import { resolveRouteBindings } from './resolveRouteBindings'; +import { IsolatedApiProvider } from '../apis/system/ApiProvider'; type CompatiblePlugin = | BackstagePlugin @@ -335,7 +336,14 @@ export class AppManager implements BackstageApp { const [identityApi, setIdentityApi] = useState(); if (!identityApi) { - return ; + // Encapsulate the sign in page component in an API provider which + // contains all APIs except the identity API. Provides fast feedback in + // case the identity API is accidentally required on the sign-in page. + return ( + + + + ); } this.appIdentityProxy.setTarget(identityApi); @@ -473,6 +481,21 @@ export class AppManager implements BackstageApp { return this.apiHolder; } + private getIdentitylessApiHolder() { + const registrySansIdentity = new ApiFactoryRegistry(); + + // Assume all APIs in the current registry are valid. + this.apiFactoryRegistry.getAllApis().forEach(apiRef => { + const factory = this.apiFactoryRegistry.get(apiRef); + // Add all API factories except identity! + if (factory && apiRef !== identityApiRef) { + registrySansIdentity.register('default', factory); + } + }); + + return new ApiResolver(registrySansIdentity); + } + private verifyPlugins(plugins: Iterable) { const pluginIds = new Set();