Hang identity api methods until implementation is set.
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -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<IdentityApi>;
|
||||
private resolveTarget: (api: IdentityApi) => void = () => {};
|
||||
|
||||
constructor() {
|
||||
this.waitForTarget = new Promise<IdentityApi>(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<ProfileInfo> {
|
||||
if (!this.target) {
|
||||
throw mkError('getProfileInfo');
|
||||
}
|
||||
return this.target.getProfileInfo();
|
||||
return this.waitForTarget.then(target => target.getProfileInfo());
|
||||
}
|
||||
|
||||
async getBackstageIdentity(): Promise<BackstageUserIdentity> {
|
||||
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<string | undefined> {
|
||||
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<void> {
|
||||
if (!this.target) {
|
||||
throw mkError('signOut');
|
||||
}
|
||||
await this.target.signOut();
|
||||
await this.waitForTarget.then(target => target.signOut());
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,24 @@ export const ApiProvider = (props: PropsWithChildren<ApiProviderProps>) => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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<ApiProviderProps>,
|
||||
) => {
|
||||
const { apis, children } = props;
|
||||
return (
|
||||
<ApiContext.Provider
|
||||
value={createVersionedValueMap({ 1: apis })}
|
||||
children={children}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ApiProvider.propTypes = {
|
||||
apis: PropTypes.shape({ get: PropTypes.func.isRequired }).isRequired,
|
||||
children: PropTypes.node,
|
||||
|
||||
@@ -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<any, any>
|
||||
@@ -335,7 +336,14 @@ export class AppManager implements BackstageApp {
|
||||
const [identityApi, setIdentityApi] = useState<IdentityApi>();
|
||||
|
||||
if (!identityApi) {
|
||||
return <Component onSignInSuccess={setIdentityApi} />;
|
||||
// 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 (
|
||||
<IsolatedApiProvider apis={this.getIdentitylessApiHolder()}>
|
||||
<Component onSignInSuccess={setIdentityApi} />
|
||||
</IsolatedApiProvider>
|
||||
);
|
||||
}
|
||||
|
||||
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<CompatiblePlugin>) {
|
||||
const pluginIds = new Set<string>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user