From 97a077583a82f54904254c477388708a121898d0 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 26 Aug 2024 14:28:48 +0200 Subject: [PATCH] chore: move the appIdentityProxy across MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Patrik Oldsberg Co-authored-by: Johan Haals Signed-off-by: blam --- .../frontend-app-api/src/wiring/createApp.tsx | 45 +++-------- plugins/app/src/extensions/AppRoot.tsx | 76 ++++++++++++++++--- 2 files changed, 79 insertions(+), 42 deletions(-) diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 51d824ba67..537c6c1d91 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -42,18 +42,12 @@ import { configApiRef, featureFlagsApiRef, identityApiRef, - errorApiRef, - discoveryApiRef, - fetchApiRef, } from '@backstage/core-plugin-api'; import { getAvailableFeatures } from './discovery'; import { ApiFactoryRegistry, ApiResolver } from '@backstage/core-app-api'; // TODO: Get rid of all of these -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { isProtectedApp } from '../../../core-app-api/src/app/isProtectedApp'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; + // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { defaultConfigLoaderSync } from '../../../core-app-api/src/app/defaultConfigLoader'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -67,7 +61,6 @@ import { CreateAppRouteBinder } from '../routing'; import { RouteResolver } from '../routing/RouteResolver'; import { resolveRouteBindings } from '../routing/resolveRouteBindings'; import { collectRouteIds } from '../routing/collectRouteIds'; -import { InternalAppContext } from './InternalAppContext'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalBackstagePlugin } from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -81,6 +74,9 @@ import { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig'; import { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; +import { BackstageRouteObject } from '../routing/types'; function deduplicateFeatures( allFeatures: FrontendFeature[], @@ -215,6 +211,7 @@ class AppTreeApiProxy implements AppTreeApi { // Helps delay callers from reaching out to the API before the app tree has been materialized class RouteResolutionApiProxy implements RouteResolutionApi { #delegate: RouteResolutionApi | undefined; + #routeObjects: BackstageRouteObject[] | undefined; constructor( private readonly tree: AppTree, @@ -251,9 +248,14 @@ class RouteResolutionApiProxy implements RouteResolutionApi { this.routeBindings, this.basePath, ); + this.#routeObjects = routeInfo.routeObjects; return routeInfo; } + + getRouteObjects() { + return this.#routeObjects; + } } /** @@ -285,7 +287,6 @@ export function createSpecializedApp(options?: { ); const factories = createApiFactories({ tree }); - const appTreeApi = new AppTreeApiProxy(tree); const routeResolutionApi = new RouteResolutionApiProxy( tree, @@ -312,25 +313,9 @@ export function createSpecializedApp(options?: { instantiateAppNodeTree(appNode, apiHolder); } - const routeInfo = routeResolutionApi.initialize(); + routeResolutionApi.initialize(); appTreeApi.initialize(); - if (isProtectedApp()) { - const discoveryApi = apiHolder.get(discoveryApiRef); - const errorApi = apiHolder.get(errorApiRef); - const fetchApi = apiHolder.get(fetchApiRef); - if (!discoveryApi || !errorApi || !fetchApi) { - throw new Error( - 'App is running in protected mode but missing required APIs', - ); - } - appIdentityProxy.enableCookieAuth({ - discoveryApi, - errorApi, - fetchApi, - }); - } - const featureFlagApi = apiHolder.get(featureFlagsApiRef); if (featureFlagApi) { for (const feature of features) { @@ -354,13 +339,7 @@ export function createSpecializedApp(options?: { .get('app')![0] .instance!.getData(coreExtensionData.reactElement); - const AppComponent = () => ( - - {rootEl} - - ); + const AppComponent = () => rootEl; return { createRoot() { diff --git a/plugins/app/src/extensions/AppRoot.tsx b/plugins/app/src/extensions/AppRoot.tsx index e54699fac5..9906480ad6 100644 --- a/plugins/app/src/extensions/AppRoot.tsx +++ b/plugins/app/src/extensions/AppRoot.tsx @@ -19,7 +19,6 @@ import React, { Fragment, PropsWithChildren, ReactNode, - useContext, useState, } from 'react'; import { @@ -27,19 +26,26 @@ import { RouterBlueprint, SignInPageBlueprint, coreExtensionData, + discoveryApiRef, + fetchApiRef, + errorApiRef, createExtension, createExtensionInput, + routeResolutionApiRef, } from '@backstage/frontend-plugin-api'; import { + DiscoveryApi, + ErrorApi, + FetchApi, IdentityApi, + ProfileInfo, SignInPageProps, configApiRef, + identityApiRef, useApi, } from '@backstage/core-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { InternalAppContext } from '../../../../packages/frontend-app-api/src/wiring/InternalAppContext'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AppIdentityProxy } from '../../../../packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; +import { isProtectedApp } from '../../../../packages/core-app-api/src/app/isProtectedApp'; import { BrowserRouter } from 'react-router-dom'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { RouteTracker } from '../../../../packages/frontend-app-api/src/routing/RouteTracker'; @@ -68,7 +74,32 @@ export const AppRoot = createExtension({ ]), }, output: [coreExtensionData.reactElement], - factory({ inputs }) { + factory({ inputs, apis }) { + const identityApi = apis.get(identityApiRef); + if (!identityApi) { + throw new Error('App requires an Identity API implementation'); + } + if (!('enableCookieAuth' in identityApi)) { + throw new Error('Unexpected Identity API implementation'); + } + const appIdentityProxy = identityApi as AppIdentityProxy; + + if (isProtectedApp()) { + const discoveryApi = apis.get(discoveryApiRef); + const errorApi = apis.get(errorApiRef); + const fetchApi = apis.get(fetchApiRef); + if (!discoveryApi || !errorApi || !fetchApi) { + throw new Error( + 'App is running in protected mode but missing required APIs', + ); + } + appIdentityProxy.enableCookieAuth({ + discoveryApi, + errorApi, + fetchApi, + }); + } + let content: React.ReactNode = ( <> {inputs.elements.map(el => ( @@ -88,6 +119,7 @@ export const AppRoot = createExtension({ return [ coreExtensionData.reactElement( {children}; } +type AppIdentityProxy = IdentityApi & { + enableCookieAuth(ctx: { + errorApi: ErrorApi; + fetchApi: FetchApi; + discoveryApi: DiscoveryApi; + }): void; + setTarget( + impl: IdentityApi & /* backwards compat stuff */ { + getUserId?(): string; + getIdToken?(): Promise; + getProfile?(): ProfileInfo; + }, + options: { signOutTargetUrl: string }, + ): void; +}; + +type RouteResolverProxy = { + getRouteObjects(): any[]; +}; + /** * Props for the {@link AppRouter} component. * @public */ export interface AppRouterProps { children?: ReactNode; + appIdentityProxy: AppIdentityProxy; SignInPageComponent?: ComponentType; RouterComponent?: ComponentType>; } @@ -155,17 +208,22 @@ function DefaultRouter(props: PropsWithChildren<{}>) { export function AppRouter(props: AppRouterProps) { const { children, + appIdentityProxy, SignInPageComponent, RouterComponent = DefaultRouter, } = props; const configApi = useApi(configApiRef); + const routeResolutionsApi = useApi(routeResolutionApiRef); const basePath = getBasePath(configApi); - const internalAppContext = useContext(InternalAppContext); - if (!internalAppContext) { - throw new Error('AppRouter must be rendered within the AppProvider'); + + // TODO: Private access for now, probably replace with path -> node lookup method on the API + if (!('getRouteObjects' in routeResolutionsApi)) { + throw new Error('Unexpected route resolution API implementation'); } - const { routeObjects, appIdentityProxy } = internalAppContext; + const routeObjects = ( + routeResolutionsApi as RouteResolverProxy + ).getRouteObjects(); // If the app hasn't configured a sign-in page, we just continue as guest. if (!SignInPageComponent) {