diff --git a/.changeset/prepare-specialized-app-signin-flow.md b/.changeset/prepare-specialized-app-signin-flow.md index 31d5bb1b8d..746df0d4fe 100644 --- a/.changeset/prepare-specialized-app-signin-flow.md +++ b/.changeset/prepare-specialized-app-signin-flow.md @@ -3,4 +3,4 @@ '@backstage/frontend-defaults': patch --- -Adds `prepareSpecializedApp` as a new two-phase app wiring API for rendering a sign-in page before full app finalization. Session preparation now resolves to an opaque reusable `sessionState`, which is returned from `getSignIn().ready` and from `finalize()`, and can be passed into a future `prepareSpecializedApp` call to skip sign-in and reuse the prepared session. The existing `createSpecializedApp` API is now deprecated and backed by `prepareSpecializedApp().finalize()`, while `createApp` has been updated to use the same prepare/finalize flow. +Adds `prepareSpecializedApp` as a new two-phase app wiring API for rendering a sign-in page before full app finalization. The sign-in step is now exposed as a bootstrap component whose `onReady` callback signals that the prepared app can now be finalized, while the opaque reusable `sessionState` is stored internally on the prepared app and returned from `tryFinalize()` and `finalize()`. That session state can also be passed into a future `prepareSpecializedApp` call to skip sign-in and reuse the prepared session. The existing `createSpecializedApp` API is now deprecated and backed by `prepareSpecializedApp().finalize()`, while `createApp` has been updated to use the same prepare/finalize flow. diff --git a/packages/frontend-app-api/src/wiring/index.ts b/packages/frontend-app-api/src/wiring/index.ts index 56851ac88a..d4db40bfee 100644 --- a/packages/frontend-app-api/src/wiring/index.ts +++ b/packages/frontend-app-api/src/wiring/index.ts @@ -15,8 +15,11 @@ */ export { + type FinalizedSpecializedApp, prepareSpecializedApp, type PreparedSpecializedApp, + type PreparedSpecializedAppSignInProps, + type SpecializedAppSessionState, createSpecializedApp, type CreateSpecializedAppOptions, } from './createSpecializedApp'; diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx index 75d33282e7..38eadf6509 100644 --- a/packages/frontend-defaults/src/createApp.tsx +++ b/packages/frontend-defaults/src/createApp.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { JSX, lazy, ReactNode, Suspense, useEffect, useState } from 'react'; +import { JSX, lazy, ReactNode, Suspense, useReducer, useState } from 'react'; import { ConfigApi, coreExtensionData, @@ -30,6 +30,7 @@ import { overrideBaseUrlConfigs } from '../../core-app-api/src/app/overrideBaseU import { ConfigReader } from '@backstage/config'; import { CreateAppRouteBinder, + FinalizedSpecializedApp, prepareSpecializedApp, PreparedSpecializedApp, FrontendPluginInfoResolver, @@ -126,18 +127,9 @@ export function createApp(options?: CreateAppOptions): { bindRoutes: options?.bindRoutes, advanced: options?.advanced, }); - const signIn = preparedApp.getSignIn(); - - if (signIn.element) { - return { - default: () => , - }; - } - - const { sessionState } = await signIn.ready; return { - default: () => renderFinalizedApp(preparedApp.finalize(sessionState)), + default: () => , }; } @@ -158,51 +150,34 @@ function PreparedAppRoot(props: { preparedApp: PreparedSpecializedApp; }): JSX.Element { const signIn = props.preparedApp.getSignIn(); + const SignIn = signIn.Component; const [finalizeError, setFinalizeError] = useState(); - const [finalizedApp, setFinalizedApp] = useState< - ReturnType | undefined - >(undefined); - - useEffect(() => { - let cancelled = false; - const runFinalize = async () => { - try { - const { sessionState } = await signIn.ready; - if (cancelled) { - return; - } - setFinalizedApp(props.preparedApp.finalize(sessionState)); - } catch (error) { - if (cancelled) { - return; - } - setFinalizeError(error as Error); - } - }; - void runFinalize(); - return () => { - cancelled = true; - }; - }, [props.preparedApp, signIn]); + const [, triggerRerender] = useReducer((count: number) => count + 1, 0); if (finalizeError) { throw finalizeError; } + const finalizedApp: FinalizedSpecializedApp | undefined = + props.preparedApp.tryFinalize(); + if (!finalizedApp) { - return signIn.element ?? <>; + return ( + { + triggerRerender(); + }} + onError={setFinalizeError} + /> + ); } - return renderFinalizedApp(finalizedApp); -} - -function renderFinalizedApp( - app: ReturnType, -) { - const errorPage = maybeCreateErrorPage(app); + const errorPage = maybeCreateErrorPage(finalizedApp); if (errorPage) { return errorPage; } - return app.tree.root.instance!.getData(coreExtensionData.reactElement)!; + return finalizedApp.tree.root.instance!.getData( + coreExtensionData.reactElement, + )!; }