diff --git a/.changeset/varldens-starkaste-bjorn.md b/.changeset/varldens-starkaste-bjorn.md new file mode 100644 index 0000000000..5b147395fb --- /dev/null +++ b/.changeset/varldens-starkaste-bjorn.md @@ -0,0 +1,7 @@ +--- +'@backstage/core-app-api': patch +--- + +Asynchronous methods on the identity API can now reliably be called at any time, including early in the bootstrap process or prior to successful sign-in. + +Previously in such situations, a `Tried to access IdentityApi before app was loaded` error would be thrown. Now, those methods will wait and resolve eventually (as soon as a concrete identity API is provided). 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..3ef3a1f6d3 100644 --- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts @@ -47,10 +47,19 @@ type CompatibilityIdentityApi = IdentityApi & { */ export class AppIdentityProxy implements IdentityApi { private target?: CompatibilityIdentityApi; + private waitForTarget: Promise; + private resolveTarget: (api: CompatibilityIdentityApi) => 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,13 @@ 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 +104,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 => { + 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(); } }