diff --git a/.changeset/neat-zebras-run.md b/.changeset/neat-zebras-run.md new file mode 100644 index 0000000000..4ce5d5c393 --- /dev/null +++ b/.changeset/neat-zebras-run.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Fixed an issue where an explicit port the frontend base URL could break the app. diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx index 1b2ac9471a..db0d3ea9af 100644 --- a/packages/core-app-api/src/app/AppManager.test.tsx +++ b/packages/core-app-api/src/app/AppManager.test.tsx @@ -634,6 +634,28 @@ describe('Integration Test', () => { }, }, ], + [ + ['http://localhost', 'http://localhost'], + { + backend: { + baseUrl: 'http://localhost:80', + }, + app: { + baseUrl: 'http://localhost:80', + }, + }, + ], + [ + ['http://localhost', 'http://localhost'], + { + backend: { + baseUrl: 'http://localhost', + }, + app: { + baseUrl: 'http://localhost', + }, + }, + ], [ ['http://localhost/backstage', 'http://localhost/backstage'], { @@ -674,7 +696,7 @@ describe('Integration Test', () => { }, ], ])( - 'should be %p when %p', + 'should be %p when %p (%#)', async ([expectedAppUrl, expectedBackendUrl], data) => { const app = new AppManager({ apis: [], diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index d5f8210185..33d43cbca6 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -82,6 +82,17 @@ 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, @@ -121,27 +132,6 @@ function useConfigLoader( if (config.value?.length) { const urlConfigReader = ConfigReader.fromConfigs(config.value); - /** - * Return the origin of the given URL. - * @param url An absolute URL. - * @returns The given URL's origin. - * @throws If fullUrl is not a correctly formatted absolute URL. - */ - const getOrigin = (url: string) => new URL(url).origin; - - /** - * Resolve an absolute URL as relative to the current document. - * @param fullUrl URL to resolve. - * @returns Absolute URL with origin as the current document origin. - * @throws If fullUrl is not a correctly formatted absolute URL. - */ - const overrideOrigin = (fullUrl: string) => { - return new URL( - fullUrl.replace(getOrigin(fullUrl), ''), - document.location.origin, - ).href.replace(/\/$/, ''); - }; - /** * Test configs may not define `app.baseUrl` or `backend.baseUrl` and we * don't want to enforce here. @@ -155,18 +145,18 @@ function useConfigLoader( context: 'relative-resolver', }; if (appBaseUrl && backendBaseUrl) { - const appOrigin = getOrigin(appBaseUrl); - const backendOrigin = getOrigin(backendBaseUrl); + const appOrigin = new URL(appBaseUrl).origin; + const backendOrigin = new URL(backendBaseUrl).origin; if (appOrigin === backendOrigin) { - const newBackendBaseUrl = overrideOrigin(backendBaseUrl); + const newBackendBaseUrl = createLocalBaseUrl(backendBaseUrl); if (backendBaseUrl !== newBackendBaseUrl) { relativeResolverConfig.data.backend = { baseUrl: newBackendBaseUrl }; } } } if (appBaseUrl) { - const newAppBaseUrl = overrideOrigin(appBaseUrl); + const newAppBaseUrl = createLocalBaseUrl(appBaseUrl); if (appBaseUrl !== newAppBaseUrl) { relativeResolverConfig.data.app = { baseUrl: newAppBaseUrl }; }