diff --git a/frontend/src/app/(editor)/[id]/page.tsx b/frontend/src/app/(editor)/[id]/page.tsx index 29422d35e..cbc6ca4e0 100644 --- a/frontend/src/app/(editor)/[id]/page.tsx +++ b/frontend/src/app/(editor)/[id]/page.tsx @@ -5,8 +5,8 @@ */ import { getNote } from '../../../api/notes' import { redirect } from 'next/navigation' -import { baseUrlFromEnvExtractor } from '../../../utils/base-url-from-env-extractor' import { notFound } from 'next/navigation' +import { extractBaseUrls } from '../../../utils/base-url-from-env-extractor' interface PageProps { params: { id: string | undefined } @@ -16,7 +16,7 @@ interface PageProps { * Redirects the user to the editor if the link is a root level direct link to a version 1 note. */ const DirectLinkFallback = async ({ params }: PageProps) => { - const baseUrl = baseUrlFromEnvExtractor.extractBaseUrls().editor + const baseUrl = extractBaseUrls().editor if (params.id === undefined) { notFound() diff --git a/frontend/src/app/(editor)/layout.tsx b/frontend/src/app/(editor)/layout.tsx index e9822556c..6a8e96241 100644 --- a/frontend/src/app/(editor)/layout.tsx +++ b/frontend/src/app/(editor)/layout.tsx @@ -12,7 +12,7 @@ import { DarkMode } from '../../components/layout/dark-mode/dark-mode' import { ExpectedOriginBoundary } from '../../components/layout/expected-origin-boundary' import { UiNotificationBoundary } from '../../components/notifications/ui-notification-boundary' import { StoreProvider } from '../../redux/store-provider' -import { baseUrlFromEnvExtractor } from '../../utils/base-url-from-env-extractor' +import { extractBaseUrls } from '../../utils/base-url-from-env-extractor' import { configureLuxon } from '../../utils/configure-luxon' import type { Metadata } from 'next' import React from 'react' @@ -21,7 +21,7 @@ import { getConfig } from '../../api/config' configureLuxon() export default async function RootLayout({ children }: { children: React.ReactNode }) { - const baseUrls = baseUrlFromEnvExtractor.extractBaseUrls() + const baseUrls = extractBaseUrls() const frontendConfig = await getConfig(baseUrls.editor) return ( diff --git a/frontend/src/app/(render)/layout.tsx b/frontend/src/app/(render)/layout.tsx index 8afb48915..3c34b2e18 100644 --- a/frontend/src/app/(render)/layout.tsx +++ b/frontend/src/app/(render)/layout.tsx @@ -9,12 +9,12 @@ import { BaseUrlContextProvider } from '../../components/common/base-url/base-ur import { FrontendConfigContextProvider } from '../../components/common/frontend-config-context/frontend-config-context-provider' import { ExpectedOriginBoundary } from '../../components/layout/expected-origin-boundary' import { StoreProvider } from '../../redux/store-provider' -import { baseUrlFromEnvExtractor } from '../../utils/base-url-from-env-extractor' +import { extractBaseUrls } from '../../utils/base-url-from-env-extractor' import React from 'react' import { getConfig } from '../../api/config' export default async function RootLayout({ children }: { children: React.ReactNode }) { - const baseUrls = baseUrlFromEnvExtractor.extractBaseUrls() + const baseUrls = extractBaseUrls() const frontendConfig = await getConfig(baseUrls.renderer) return ( diff --git a/frontend/src/utils/base-url-from-env-extractor.spec.ts b/frontend/src/utils/base-url-from-env-extractor.spec.ts index cbf66b3af..d2f47361b 100644 --- a/frontend/src/utils/base-url-from-env-extractor.spec.ts +++ b/frontend/src/utils/base-url-from-env-extractor.spec.ts @@ -3,15 +3,22 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { BaseUrlFromEnvExtractor } from './base-url-from-env-extractor' + +import type { BaseUrls } from '../components/common/base-url/base-url-context-provider' describe('BaseUrlFromEnvExtractor', () => { + let extractBaseUrls: () => BaseUrls + + beforeEach(async () => { + jest.resetModules() + extractBaseUrls = (await import('./base-url-from-env-extractor')).extractBaseUrls + }) + it('should return the base urls if both are valid urls', () => { process.env.HD_BASE_URL = 'https://editor.example.org/' process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/' - const sut = new BaseUrlFromEnvExtractor() - expect(sut.extractBaseUrls()).toStrictEqual({ + expect(extractBaseUrls()).toStrictEqual({ renderer: 'https://renderer.example.org/', editor: 'https://editor.example.org/' }) @@ -20,33 +27,29 @@ describe('BaseUrlFromEnvExtractor', () => { it('should return an empty optional if no var is set', () => { process.env.HD_BASE_URL = undefined process.env.HD_RENDERER_BASE_URL = undefined - const sut = new BaseUrlFromEnvExtractor() - expect(() => sut.extractBaseUrls()).toThrow() + expect(() => extractBaseUrls()).toThrow() }) it("should return an empty optional if editor base url isn't an URL", () => { process.env.HD_BASE_URL = 'bibedibabedibu' process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/' - const sut = new BaseUrlFromEnvExtractor() - expect(() => sut.extractBaseUrls()).toThrow() + expect(() => extractBaseUrls()).toThrow() }) it("should return an empty optional if renderer base url isn't an URL", () => { process.env.HD_BASE_URL = 'https://editor.example.org/' process.env.HD_RENDERER_BASE_URL = 'bibedibabedibu' - const sut = new BaseUrlFromEnvExtractor() - expect(() => sut.extractBaseUrls()).toThrow() + expect(() => extractBaseUrls()).toThrow() }) it("should return an optional if editor base url isn't ending with a slash", () => { process.env.HD_BASE_URL = 'https://editor.example.org' process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/' - const sut = new BaseUrlFromEnvExtractor() - expect(sut.extractBaseUrls()).toStrictEqual({ + expect(extractBaseUrls()).toStrictEqual({ renderer: 'https://renderer.example.org/', editor: 'https://editor.example.org/' }) @@ -55,9 +58,8 @@ describe('BaseUrlFromEnvExtractor', () => { it("should return an optional if renderer base url isn't ending with a slash", () => { process.env.HD_BASE_URL = 'https://editor.example.org/' process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org' - const sut = new BaseUrlFromEnvExtractor() - expect(sut.extractBaseUrls()).toStrictEqual({ + expect(extractBaseUrls()).toStrictEqual({ renderer: 'https://renderer.example.org/', editor: 'https://editor.example.org/' }) @@ -66,9 +68,8 @@ describe('BaseUrlFromEnvExtractor', () => { it('should copy editor base url to renderer base url if renderer base url is omitted', () => { process.env.HD_BASE_URL = 'https://editor.example.org/' delete process.env.HD_RENDERER_BASE_URL - const sut = new BaseUrlFromEnvExtractor() - expect(sut.extractBaseUrls()).toStrictEqual({ + expect(extractBaseUrls()).toStrictEqual({ renderer: 'https://editor.example.org/', editor: 'https://editor.example.org/' }) diff --git a/frontend/src/utils/base-url-from-env-extractor.ts b/frontend/src/utils/base-url-from-env-extractor.ts index 72a74b7f4..04b11d418 100644 --- a/frontend/src/utils/base-url-from-env-extractor.ts +++ b/frontend/src/utils/base-url-from-env-extractor.ts @@ -9,88 +9,81 @@ import { isTestMode, isBuildTime } from './test-modes' import { NoSubdirectoryAllowedError, parseUrl } from '@hedgedoc/commons' import { Optional } from '@mrdrogdrog/optional' -/** - * Extracts and caches the editor and renderer base urls from the environment variables. - */ -export class BaseUrlFromEnvExtractor { - private baseUrls: BaseUrls | undefined - private readonly logger = new Logger('Base URL Configuration') +let baseUrls: BaseUrls | undefined = undefined +const logger = new Logger('Base URL Configuration') - private extractUrlFromEnvVar(envVarName: string, envVarValue: string | undefined): Optional { - try { - return parseUrl(envVarValue) - } catch (error) { - if (error instanceof NoSubdirectoryAllowedError) { - this.logger.error(error.message) - return Optional.empty() - } else { - throw error - } +function extractUrlFromEnvVar(envVarName: string, envVarValue: string | undefined): Optional { + try { + return parseUrl(envVarValue) + } catch (error) { + if (error instanceof NoSubdirectoryAllowedError) { + logger.error(error.message) + return Optional.empty() + } else { + throw error } } - - private extractEditorBaseUrlFromEnv(): Optional { - const envValue = this.extractUrlFromEnvVar('HD_BASE_URL', process.env.HD_BASE_URL) - if (envValue.isEmpty()) { - this.logger.error("HD_BASE_URL isn't a valid URL!") - } - return envValue - } - - private extractRendererBaseUrlFromEnv(editorBaseUrl: URL): Optional { - if (isTestMode) { - this.logger.info('Test mode activated. Using editor base url for renderer.') - return Optional.of(editorBaseUrl) - } - - if (!process.env.HD_RENDERER_BASE_URL) { - this.logger.info('HD_RENDERER_BASE_URL is unset. Using editor base url for renderer.') - return Optional.of(editorBaseUrl) - } - - return this.extractUrlFromEnvVar('HD_RENDERER_BASE_URL', process.env.HD_RENDERER_BASE_URL) - } - - private renewBaseUrls(): BaseUrls { - return this.extractEditorBaseUrlFromEnv() - .flatMap((editorBaseUrl) => - this.extractRendererBaseUrlFromEnv(editorBaseUrl).map((rendererBaseUrl) => { - return { - editor: editorBaseUrl.toString(), - renderer: rendererBaseUrl.toString() - } - }) - ) - .orElseThrow(() => new Error('couldnt parse env vars')) - } - - /** - * Extracts the editor and renderer base urls from the environment variables. - * - * @return An {@link Optional} with the base urls. - */ - public extractBaseUrls(): BaseUrls { - if (isBuildTime) { - return { - editor: 'https://example.org/', - renderer: 'https://example.org/' - } - } - - if (this.baseUrls === undefined) { - this.baseUrls = this.renewBaseUrls() - this.logBaseUrls() - } - return this.baseUrls - } - - private logBaseUrls() { - if (this.baseUrls === undefined) { - return - } - this.logger.info('Editor base URL', this.baseUrls.editor.toString()) - this.logger.info('Renderer base URL', this.baseUrls.renderer.toString()) - } } -export const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor() +function extractEditorBaseUrlFromEnv(): Optional { + const envValue = extractUrlFromEnvVar('HD_BASE_URL', process.env.HD_BASE_URL) + if (envValue.isEmpty()) { + logger.error("HD_BASE_URL isn't a valid URL!") + } + return envValue +} + +function extractRendererBaseUrlFromEnv(editorBaseUrl: URL): Optional { + if (isTestMode) { + logger.info('Test mode activated. Using editor base url for renderer.') + return Optional.of(editorBaseUrl) + } + + if (!process.env.HD_RENDERER_BASE_URL) { + logger.info('HD_RENDERER_BASE_URL is unset. Using editor base url for renderer.') + return Optional.of(editorBaseUrl) + } + + return extractUrlFromEnvVar('HD_RENDERER_BASE_URL', process.env.HD_RENDERER_BASE_URL) +} + +function renewBaseUrls(): BaseUrls { + return extractEditorBaseUrlFromEnv() + .flatMap((editorBaseUrl) => + extractRendererBaseUrlFromEnv(editorBaseUrl).map((rendererBaseUrl) => { + return { + editor: editorBaseUrl.toString(), + renderer: rendererBaseUrl.toString() + } + }) + ) + .orElseThrow(() => new Error('couldnt parse env vars')) +} + +function logBaseUrls() { + if (baseUrls === undefined) { + return + } + logger.info('Editor base URL', baseUrls.editor.toString()) + logger.info('Renderer base URL', baseUrls.renderer.toString()) +} + +/** + * Extracts the editor and renderer base urls from the environment variables. + * + * @return An {@link Optional} with the base urls. + */ +export function extractBaseUrls(): BaseUrls { + if (isBuildTime) { + return { + editor: 'https://example.org/', + renderer: 'https://example.org/' + } + } + + if (baseUrls === undefined) { + baseUrls = renewBaseUrls() + logBaseUrls() + } + return baseUrls +}