diff --git a/adapter/src/utils/getBaseUrl.js b/adapter/src/utils/getBaseUrl.js new file mode 100644 index 00000000..035c7895 --- /dev/null +++ b/adapter/src/utils/getBaseUrl.js @@ -0,0 +1,47 @@ +const isUrlAbsolute = (url) => { + // the URL constructor will throw for relative URLs + try { + new URL(url) + return true + } catch { + return false + } +} + +/** + * Deduces the base URL for the DHIS2 instance from the location at which this + * app is hosted. Returns an absolute URL + * @param {string} defaultBaseUrl a fallback URL that will be used if the base + * URL can't be deduced from the window location + * @returns {string} an absolute base URL for the DHIS2 instance + */ +export const getBaseUrl = (defaultBaseUrl) => { + // if defaultBaseUrl is absolute, use that + if (isUrlAbsolute(defaultBaseUrl)) { + return defaultBaseUrl + } + + const { location } = window + const path = location.pathname + + // get penultimate 3 path elements (don't need anything before), + // e.g. /dev/api/apps/app-name/index.html => ['api', 'apps', 'app-name']. + const splitPath = path.split('/').slice(-4, -1) + // could be shorter than 3 elements for e.g. /dhis-web-maps/ + const l = splitPath.length + + // test for core apps + if (/dhis-web-.+/.test(splitPath[l - 1])) { + return new URL('..', location.href).href + } + + // test for custom apps + if (splitPath[l - 3] === 'api' && splitPath[l - 2] === 'apps') { + return new URL('../../..', location.href).href + } + + // todo: handle path for global shell + + // otherwise, return absolute version of default + return new URL(defaultBaseUrl, location.href).href +} diff --git a/adapter/src/utils/getBaseUrl.test.js b/adapter/src/utils/getBaseUrl.test.js new file mode 100644 index 00000000..6f4ec5e3 --- /dev/null +++ b/adapter/src/utils/getBaseUrl.test.js @@ -0,0 +1,61 @@ +import { getBaseUrl } from './getBaseUrl.js' + +const testOrigin = 'https://debug.dhis2.org' +// { [testPath]: expectedPath } +const testPaths = { + '/dev/api/apps/simple-app/index.html': '/dev/', + '/analytics_dev/dhis-web-maps/plugin.html': '/analytics_dev/', + '/dhis-web-line-listing/index.html': '/', + '/dhis-web-user-settings/': '/', + '/hmis/staging/v41/api/apps/WHO-Data-Quality-App/app.html': + '/hmis/staging/v41/', +} + +const windowLocationMock = jest + .spyOn(window, 'location', 'get') + .mockImplementation(() => ({ + href: 'https://debug.dhis2.org/dev/', + pathname: '/dev/', + })) + +afterEach(() => { + jest.clearAllMocks() +}) + +test('an absolute URL is used if provided as the default', () => { + const testBaseUrl = 'http://localhost:8080/hmis/long/path/dev' + + const url = getBaseUrl(testBaseUrl) + + expect(url).toBe(testBaseUrl) +}) + +describe('location variants', () => { + const defaultRelativeBaseUrl = '..' + + for (const [testPath, expectedPath] of Object.entries(testPaths)) { + test(testPath, () => { + windowLocationMock.mockImplementation(() => ({ + pathname: testPath, + href: testOrigin + testPath, + })) + + const url = getBaseUrl(defaultRelativeBaseUrl) + + expect(url).toBe(testOrigin + expectedPath) + }) + } +}) + +test("if the function doesn't match a pattern, it falls back to the provided default", () => { + const relativeDefaultBaseUrl = '../../..' + const testPath = '/not/a/recognized/path/index.html' + windowLocationMock.mockImplementation(() => ({ + pathname: testPath, + href: testOrigin + testPath, + })) + + const url = getBaseUrl(relativeDefaultBaseUrl) + + expect(url).toBe(testOrigin + '/not/') +})