diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index de078c0ffc..0bd495c03b 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AppConfig, Config } from '@backstage/config'; +import { Config } from '@backstage/config'; import React, { ComponentType, PropsWithChildren, @@ -81,6 +81,7 @@ import { InternalAppContext } from './InternalAppContext'; import { AppRouter, getBasePath } from './AppRouter'; import { AppTranslationProvider } from './AppTranslationProvider'; import { AppTranslationApiImpl } from '../apis/implementations/AppTranslationApi'; +import { overrideBaseUrlConfigs } from './overrideBaseUrlConfigs'; type CompatiblePlugin = | BackstagePlugin @@ -88,17 +89,6 @@ type CompatiblePlugin = output(): Array<{ type: 'feature-flag'; name: string }>; }); -/** - * Creates a base URL that uses to the current document origin. - */ -function createLocalBaseUrl(fullUrl: string): string { - const url = new URL(fullUrl); - url.protocol = document.location.protocol; - url.hostname = document.location.hostname; - url.port = document.location.port; - return url.toString().replace(/\/$/, ''); -} - function useConfigLoader( configLoader: AppConfigLoader | undefined, components: AppComponents, @@ -131,52 +121,9 @@ function useConfigLoader( }; } - let configReader; - /** - * config.value can be undefined or empty. If it's either, don't bother overriding anything. - */ - if (config.value?.length) { - const urlConfigReader = ConfigReader.fromConfigs(config.value); - - /** - * Test configs may not define `app.baseUrl` or `backend.baseUrl` and we - * don't want to enforce here. - */ - const appBaseUrl = urlConfigReader.getOptionalString('app.baseUrl'); - const backendBaseUrl = urlConfigReader.getOptionalString('backend.baseUrl'); - - let configs = config.value; - const relativeResolverConfig: AppConfig = { - data: {}, - context: 'relative-resolver', - }; - if (appBaseUrl && backendBaseUrl) { - const appOrigin = new URL(appBaseUrl).origin; - const backendOrigin = new URL(backendBaseUrl).origin; - - if (appOrigin === backendOrigin) { - const newBackendBaseUrl = createLocalBaseUrl(backendBaseUrl); - if (backendBaseUrl !== newBackendBaseUrl) { - relativeResolverConfig.data.backend = { baseUrl: newBackendBaseUrl }; - } - } - } - if (appBaseUrl) { - const newAppBaseUrl = createLocalBaseUrl(appBaseUrl); - if (appBaseUrl !== newAppBaseUrl) { - relativeResolverConfig.data.app = { baseUrl: newAppBaseUrl }; - } - } - /** - * Only add the relative config if there is actually data to add. - */ - if (Object.keys(relativeResolverConfig.data).length) { - configs = configs.concat([relativeResolverConfig]); - } - configReader = ConfigReader.fromConfigs(configs); - } else { - configReader = ConfigReader.fromConfigs([]); - } + const configReader = ConfigReader.fromConfigs( + config.value?.length ? overrideBaseUrlConfigs(config.value) : [], + ); return { api: configReader }; } diff --git a/packages/core-app-api/src/app/overrideBaseUrlConfigs.ts b/packages/core-app-api/src/app/overrideBaseUrlConfigs.ts new file mode 100644 index 0000000000..5dab6db127 --- /dev/null +++ b/packages/core-app-api/src/app/overrideBaseUrlConfigs.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AppConfig, ConfigReader } from '@backstage/config'; + +/** + * Creates a base URL that uses to the current document origin. + */ +function createLocalBaseUrl(fullUrl: string): string { + const url = new URL(fullUrl); + url.protocol = document.location.protocol; + url.hostname = document.location.hostname; + url.port = document.location.port; + return url.toString().replace(/\/$/, ''); +} + +/** + * If we are able to override the app and backend base URLs to values that + * match the origin of the current location, then this function returns a + * new array of app configs that contain the overrides. + * + * @internal + */ +export function overrideBaseUrlConfigs(inputConfigs: AppConfig[]): AppConfig[] { + const urlConfigReader = ConfigReader.fromConfigs(inputConfigs); + + // In tests we may not have `app.baseUrl` or `backend.baseUrl`, to keep them optional + const appBaseUrl = urlConfigReader.getOptionalString('app.baseUrl'); + const backendBaseUrl = urlConfigReader.getOptionalString('backend.baseUrl'); + + let configs = inputConfigs; + + let newBackendBaseUrl: string | undefined = undefined; + let newAppBaseUrl: string | undefined = undefined; + + if (appBaseUrl && backendBaseUrl) { + const appOrigin = new URL(appBaseUrl).origin; + const backendOrigin = new URL(backendBaseUrl).origin; + + if (appOrigin === backendOrigin) { + const maybeNewBackendBaseUrl = createLocalBaseUrl(backendBaseUrl); + if (backendBaseUrl !== maybeNewBackendBaseUrl) { + newBackendBaseUrl = maybeNewBackendBaseUrl; + } + } + } + + if (appBaseUrl) { + const maybeNewAppBaseUrl = createLocalBaseUrl(appBaseUrl); + if (appBaseUrl !== maybeNewAppBaseUrl) { + newAppBaseUrl = maybeNewAppBaseUrl; + } + } + + // Only add the relative config if there is actually data to add. + if (newAppBaseUrl || newBackendBaseUrl) { + configs = configs.concat({ + data: { + app: newAppBaseUrl && { + baseUrl: newAppBaseUrl, + }, + backend: newBackendBaseUrl && { + baseUrl: newBackendBaseUrl, + }, + }, + context: 'relative-resolver', + }); + } + + return configs; +}