diff --git a/.env.template b/.env.template index 92bec95b..d8aded78 100644 --- a/.env.template +++ b/.env.template @@ -6,6 +6,7 @@ SENTRY_PROJECT= VITE_SENTRY_DSN= # BFF: BFF_SENTRY_DSN= +DYNATRACE_SCRIPT_URL= # OpenID Connect Configuration for Onboarding API diff --git a/server.ts b/server.ts index 114b6e37..266a510f 100644 --- a/server.ts +++ b/server.ts @@ -8,9 +8,15 @@ import proxy from './server/app.js'; import envPlugin from './server/config/env.js'; import { copyFileSync } from 'node:fs'; import * as Sentry from '@sentry/node'; +import { injectDynatraceTag } from './server/config/dynatrace.js'; dotenv.config(); +const { DYNATRACE_SCRIPT_URL } = process.env; +if (DYNATRACE_SCRIPT_URL) { + injectDynatraceTag(DYNATRACE_SCRIPT_URL); +} + if (!process.env.BFF_SENTRY_DSN || process.env.BFF_SENTRY_DSN.trim() === '') { console.error('Error: Sentry DSN is not provided. Sentry will not be initialized.'); } else { @@ -70,11 +76,21 @@ if (fastify.config.VITE_SENTRY_DSN && fastify.config.VITE_SENTRY_DSN.length > 0) } } +let dynatraceOrigin = ''; +if (DYNATRACE_SCRIPT_URL) { + try { + dynatraceOrigin = new URL(DYNATRACE_SCRIPT_URL).origin; + } catch { + console.error('DYNATRACE_SCRIPT_URL is not a valid URL'); + } +} + + fastify.register(helmet, { contentSecurityPolicy: { directives: { - 'connect-src': ["'self'", 'sdk.openui5.org', sentryHost], - 'script-src': isLocalDev ? ["'self'", "'unsafe-inline'"] : ["'self'"], + 'connect-src': ["'self'", 'sdk.openui5.org', sentryHost, dynatraceOrigin], + 'script-src': isLocalDev ? ["'self'", "'unsafe-inline'", dynatraceOrigin] : ["'self'", dynatraceOrigin], // @ts-ignore 'frame-ancestors': [fastify.config.FRAME_ANCESTORS], }, diff --git a/server/config/dynatrace.ts b/server/config/dynatrace.ts new file mode 100644 index 00000000..a5b953f7 --- /dev/null +++ b/server/config/dynatrace.ts @@ -0,0 +1,34 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import { join } from 'node:path'; + +export function injectDynatraceTag(scriptUrl: string): void { + const indexPath = join(process.cwd(), 'dist/client/index.html'); + console.log(`[Dynatrace] Injecting "${scriptUrl}" into "${indexPath}".`); + + let html: string; + try { + html = readFileSync(indexPath, 'utf-8'); + } catch (err) { + console.error(`[Dynatrace] Failed to read ${indexPath}.`, err); + return; + } + + if (html.includes(scriptUrl)) { + console.log('[Dynatrace] Script already present, skipping.'); + return; + } + + const scriptTag = + ``; + + // Inject right before closing tag (case-insensitive) + const headClose = /<\/head>/i; + if (!headClose.test(html)) { + console.error('[Dynatrace] tag not found, aborting.'); + return; + } + html = html.replace(headClose, ` ${scriptTag}\n`); + + writeFileSync(indexPath, html, 'utf-8'); + console.log('[Dynatrace] Script injected successfully.'); +}