diff --git a/docs/content/references/config/general.md b/docs/content/references/config/general.md index 11bbf39df..91230101a 100644 --- a/docs/content/references/config/general.md +++ b/docs/content/references/config/general.md @@ -6,6 +6,7 @@ | `HD_BACKEND_PORT` | 3000 | | The port the backend process listens on. | | `HD_FRONTEND_PORT` | 3001 | | The port the frontend process listens on. | | `HD_RENDERER_BASE_URL` | Content of HD_BASE_URL | | The URL the renderer runs on. If omitted this will be the same as `HD_BASE_URL`. For more detail see [this faq entry][faq-entry] | +| `HD_INTERNAL_API_URL` | Content of HD_BASE_URL | `http://localhost:3000` | This URL is used by the frontend to access the backend directly if it can't reach the backend using the `HD_BASE_URL` | | `HD_LOGLEVEL` | warn | | The loglevel that should be used. Options are `error`, `warn`, `info`, `debug` or `trace`. | | `HD_SHOW_LOG_TIMESTAMP` | true | | Specifies if a timestamp should be added to the log statements. Disabling is useful for extern log management (systemd etc.) | | `HD_FORBIDDEN_NOTE_IDS` | - | `notAllowed,alsoNotAllowed` | A list of note ids (separated by `,`), that are not allowed to be created or requested by anyone. | diff --git a/frontend/src/api/alias/index.ts b/frontend/src/api/alias/index.ts index 1662a94af..9bb398c0c 100644 --- a/frontend/src/api/alias/index.ts +++ b/frontend/src/api/alias/index.ts @@ -14,7 +14,7 @@ import type { Alias, NewAliasDto, PrimaryAliasDto } from './types' * @param noteIdOrAlias The note id or an existing alias for a note. * @param newAlias The new alias. * @return Information about the newly created alias. - * @throws {Error} when the api request wasn't successfull + * @throws {Error} when the api request wasn't successful */ export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise => { const response = await new PostApiRequestBuilder('alias') diff --git a/frontend/src/api/common/api-request-builder/api-request-builder.ts b/frontend/src/api/common/api-request-builder/api-request-builder.ts index 7c57242cb..290a23de5 100644 --- a/frontend/src/api/common/api-request-builder/api-request-builder.ts +++ b/frontend/src/api/common/api-request-builder/api-request-builder.ts @@ -8,6 +8,7 @@ import type { ApiErrorResponse } from '../api-error-response' import { ApiResponse } from '../api-response' import { defaultConfig, defaultHeaders } from '../default-config' import deepmerge from 'deepmerge' +import { baseUrlFromEnvExtractor } from '../../../utils/base-url-from-env-extractor' /** * Builder to construct and execute a call to the HTTP API. @@ -27,7 +28,18 @@ export abstract class ApiRequestBuilder { * @param baseUrl An optional base URL that is used for the endpoint */ constructor(endpoint: string, baseUrl?: string) { - this.targetUrl = `${baseUrl ?? '/'}api/private/${endpoint}` + const actualBaseUrl = this.determineBaseUrl(baseUrl) + + this.targetUrl = `${actualBaseUrl}api/private/${endpoint}` + } + + /** + * Determines the API base URL by checking if the request is made on the server or client + * + * @return the base url + */ + private determineBaseUrl(baseUrl?: string) { + return typeof window !== 'undefined' ? baseUrl ?? '/' : baseUrlFromEnvExtractor.extractBaseUrls().internalApiUrl } protected async sendRequestAndVerifyResponse(httpMethod: RequestInit['method']): Promise> { diff --git a/frontend/src/components/common/base-url/base-url-context-provider.tsx b/frontend/src/components/common/base-url/base-url-context-provider.tsx index aa4d2c926..17e8d2482 100644 --- a/frontend/src/components/common/base-url/base-url-context-provider.tsx +++ b/frontend/src/components/common/base-url/base-url-context-provider.tsx @@ -10,6 +10,7 @@ import React, { createContext } from 'react' export interface BaseUrls { renderer: string editor: string + internalApiUrl: string } interface BaseUrlContextProviderProps { 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..e9b83d112 100644 --- a/frontend/src/utils/base-url-from-env-extractor.spec.ts +++ b/frontend/src/utils/base-url-from-env-extractor.spec.ts @@ -6,71 +6,93 @@ import { BaseUrlFromEnvExtractor } from './base-url-from-env-extractor' describe('BaseUrlFromEnvExtractor', () => { - it('should return the base urls if both are valid urls', () => { + it('should return the base urls if all are valid urls', () => { process.env.HD_BASE_URL = 'https://editor.example.org/' process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/' + process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/' + const sut = new BaseUrlFromEnvExtractor() expect(sut.extractBaseUrls()).toStrictEqual({ renderer: 'https://renderer.example.org/', + internalApiUrl: 'https://internal.example.org/', editor: 'https://editor.example.org/' }) }) - it('should return an empty optional if no var is set', () => { + it('should throw if no var is set', () => { process.env.HD_BASE_URL = undefined process.env.HD_RENDERER_BASE_URL = undefined + process.env.HD_INTERNAL_API_URL = undefined const sut = new BaseUrlFromEnvExtractor() expect(() => sut.extractBaseUrls()).toThrow() }) - it("should return an empty optional if editor base url isn't an URL", () => { + it("should throw if editor base url isn't an URL", () => { process.env.HD_BASE_URL = 'bibedibabedibu' process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/' + process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/' const sut = new BaseUrlFromEnvExtractor() expect(() => sut.extractBaseUrls()).toThrow() }) - it("should return an empty optional if renderer base url isn't an URL", () => { + it("should throw if renderer base url isn't an URL", () => { process.env.HD_BASE_URL = 'https://editor.example.org/' process.env.HD_RENDERER_BASE_URL = 'bibedibabedibu' + process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/' const sut = new BaseUrlFromEnvExtractor() expect(() => sut.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' + it("should throw if internal api url isn't an URL", () => { + process.env.HD_BASE_URL = 'https://editor.example.org/' process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/' + process.env.HD_INTERNAL_API_URL = 'bibedibabedibu' const sut = new BaseUrlFromEnvExtractor() - expect(sut.extractBaseUrls()).toStrictEqual({ - renderer: 'https://renderer.example.org/', - editor: 'https://editor.example.org/' - }) + expect(() => sut.extractBaseUrls()).toThrow() }) - 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' + it('should throw if editor base url contains a subdirectory', () => { + process.env.HD_BASE_URL = 'https://editor.example.org/asd/' + process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/' + process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/' const sut = new BaseUrlFromEnvExtractor() - expect(sut.extractBaseUrls()).toStrictEqual({ - renderer: 'https://renderer.example.org/', - editor: 'https://editor.example.org/' - }) + expect(() => sut.extractBaseUrls()).toThrow() }) - it('should copy editor base url to renderer base url if renderer base url is omitted', () => { + it('should throw if renderer base url contains a subdirectory', () => { process.env.HD_BASE_URL = 'https://editor.example.org/' + process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/asd/' + process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/' + const sut = new BaseUrlFromEnvExtractor() + + expect(() => sut.extractBaseUrls()).toThrow() + }) + + it('should throw if internal api url contains a subdirectory', () => { + process.env.HD_BASE_URL = 'https://editor.example.org/' + process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/' + process.env.HD_INTERNAL_API_URL = 'https://internal.example.org/asd/' + const sut = new BaseUrlFromEnvExtractor() + + expect(() => sut.extractBaseUrls()).toThrow() + }) + + it('should copy editor base url to renderer base/internal api url if url is omitted', () => { + process.env.HD_BASE_URL = 'https://editor1.example.org/' delete process.env.HD_RENDERER_BASE_URL + delete process.env.HD_INTERNAL_API_URL const sut = new BaseUrlFromEnvExtractor() expect(sut.extractBaseUrls()).toStrictEqual({ - renderer: 'https://editor.example.org/', - editor: 'https://editor.example.org/' + renderer: 'https://editor1.example.org/', + internalApiUrl: 'https://editor1.example.org/', + editor: 'https://editor1.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..6767b0fbf 100644 --- a/frontend/src/utils/base-url-from-env-extractor.ts +++ b/frontend/src/utils/base-url-from-env-extractor.ts @@ -16,7 +16,7 @@ export class BaseUrlFromEnvExtractor { private baseUrls: BaseUrls | undefined private readonly logger = new Logger('Base URL Configuration') - private extractUrlFromEnvVar(envVarName: string, envVarValue: string | undefined): Optional { + private extractUrlFromEnvVar(envVarValue: string | undefined): Optional { try { return parseUrl(envVarValue) } catch (error) { @@ -30,36 +30,39 @@ export class BaseUrlFromEnvExtractor { } private extractEditorBaseUrlFromEnv(): Optional { - const envValue = this.extractUrlFromEnvVar('HD_BASE_URL', process.env.HD_BASE_URL) + const envValue = this.extractUrlFromEnvVar(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 { + private extractUrlFromEnv(editorBaseUrl: URL, envVarName: string): Optional { if (isTestMode) { - this.logger.info('Test mode activated. Using editor base url for renderer.') + this.logger.info(`Test mode activated. Using editor base url for ${envVarName}.`) 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.') + if (!process.env[envVarName]) { + this.logger.info(`${envVarName} is unset. Using editor base url.`) return Optional.of(editorBaseUrl) } - return this.extractUrlFromEnvVar('HD_RENDERER_BASE_URL', process.env.HD_RENDERER_BASE_URL) + return this.extractUrlFromEnvVar(process.env[envVarName]) } private renewBaseUrls(): BaseUrls { return this.extractEditorBaseUrlFromEnv() .flatMap((editorBaseUrl) => - this.extractRendererBaseUrlFromEnv(editorBaseUrl).map((rendererBaseUrl) => { - return { - editor: editorBaseUrl.toString(), - renderer: rendererBaseUrl.toString() - } - }) + this.extractUrlFromEnv(editorBaseUrl, 'HD_RENDERER_BASE_URL').flatMap((rendererBaseUrl) => + this.extractUrlFromEnv(editorBaseUrl, 'HD_INTERNAL_API_URL').map((internalApiUrl) => { + return { + editor: editorBaseUrl.toString(), + renderer: rendererBaseUrl.toString(), + internalApiUrl: internalApiUrl.toString() + } + }) + ) ) .orElseThrow(() => new Error('couldnt parse env vars')) } @@ -73,7 +76,8 @@ export class BaseUrlFromEnvExtractor { if (isBuildTime) { return { editor: 'https://example.org/', - renderer: 'https://example.org/' + renderer: 'https://example.org/', + internalApiUrl: 'https://example.org/' } } @@ -90,6 +94,7 @@ export class BaseUrlFromEnvExtractor { } this.logger.info('Editor base URL', this.baseUrls.editor.toString()) this.logger.info('Renderer base URL', this.baseUrls.renderer.toString()) + this.logger.info('Internal API URL', this.baseUrls.internalApiUrl.toString()) } }