From 691c39d67d1d2a2a24e8016c517b8dcd96d9e17f Mon Sep 17 00:00:00 2001 From: Moritz Reich Date: Wed, 3 Sep 2025 16:18:30 +0200 Subject: [PATCH 1/6] fix(discussable): added unsafe eval to script src --- server.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server.ts b/server.ts index 8092d868..8f29d536 100644 --- a/server.ts +++ b/server.ts @@ -95,7 +95,9 @@ fastify.register(helmet, { contentSecurityPolicy: { directives: { 'connect-src': ["'self'", 'sdk.openui5.org', sentryHost, dynatraceOrigin], - 'script-src': isLocalDev ? ["'self'", "'unsafe-inline'", dynatraceOrigin] : ["'self'", dynatraceOrigin], + 'script-src': isLocalDev + ? ["'self'", "'unsafe-inline'", "'unsafe-eval'", sentryHost, dynatraceOrigin] + : ["'self'", "'unsafe-eval'", sentryHost, dynatraceOrigin], // @ts-ignore 'frame-ancestors': [...fastify.config.FRAME_ANCESTORS.split(',')], }, From 590bf4b9ce2970af3a3a2625ca022ecc7fca434c Mon Sep 17 00:00:00 2001 From: Moritz Reich Date: Fri, 5 Sep 2025 13:16:01 +0200 Subject: [PATCH 2/6] fix: moved sentry information to bff supplied at runtime --- .env.template | 4 ++-- index.html | 2 +- package-lock.json | 25 +++++++++++++++++++++++-- package.json | 6 ++++-- server.ts | 15 ++++++++++++--- server/config/env.ts | 4 ++-- src/mount.ts | 14 +++++++++++--- vite.config.js | 2 ++ 8 files changed, 57 insertions(+), 15 deletions(-) diff --git a/.env.template b/.env.template index d8aded78..8969789f 100644 --- a/.env.template +++ b/.env.template @@ -1,9 +1,9 @@ -VITE_SENTRY_ENVIRONMENT= # Sentry DSN configuration, no need to be set for local setup # Frontend: SENTRY_ORG= SENTRY_PROJECT= -VITE_SENTRY_DSN= +FRONTEND_SENTRY_DSN= +FRONTEND_SENTRY_ENVIRONMENT= # BFF: BFF_SENTRY_DSN= DYNATRACE_SCRIPT_URL= diff --git a/index.html b/index.html index 6212d53f..9156cbe7 100644 --- a/index.html +++ b/index.html @@ -13,4 +13,4 @@ - \ No newline at end of file + diff --git a/package-lock.json b/package-lock.json index 4484acb0..3ebef690 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,6 +79,7 @@ "globals": "^16.0.0", "prettier": "^3.5.3", "ts-node": "^10.9.2", + "tsx": "^4.20.5", "typescript": "^5.7.3", "typescript-eslint": "^8.26.1", "vite": "^6.3.4", @@ -11212,7 +11213,7 @@ "version": "4.10.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -15216,7 +15217,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -16679,6 +16680,26 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.20.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", + "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index d8881122..67562918 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,10 @@ "npm": "^11.0.0" }, "scripts": { - "dev": "node --loader ts-node/esm ./server.ts --local-dev", + "dev": "tsx ./server.ts --local-dev", "start": "node dist/server.js", - "build": "tsc && npm run build:server && vite build", + "build": "npm run build:server && npm run build:client", + "build:client": "vite build", "build:server": "tsc -p tsconfig.server.json", "lint": "eslint ./src --report-unused-disable-directives --max-warnings 0", "lint:fix": "eslint ./src --fix", @@ -92,6 +93,7 @@ "globals": "^16.0.0", "prettier": "^3.5.3", "ts-node": "^10.9.2", + "tsx": "^4.20.5", "typescript": "^5.7.3", "typescript-eslint": "^8.26.1", "vite": "^6.3.4", diff --git a/server.ts b/server.ts index 8f29d536..574547dc 100644 --- a/server.ts +++ b/server.ts @@ -72,12 +72,12 @@ await fastify.register(envPlugin); let sentryHost = ''; // @ts-ignore -if (fastify.config.VITE_SENTRY_DSN && fastify.config.VITE_SENTRY_DSN.length > 0) { +if (fastify.config.FRONTEND_SENTRY_DSN && fastify.config.FRONTEND_SENTRY_DSN.length > 0) { try { // @ts-ignore - sentryHost = new URL(fastify.config.VITE_SENTRY_DSN).hostname; + sentryHost = new URL(fastify.config.FRONTEND_SENTRY_DSN).hostname; } catch { - console.log('VITE_SENTRY_DSN is not a valid URL'); + console.log('FRONTEND_SENTRY_DSN is not a valid URL'); sentryHost = ''; } } @@ -114,6 +114,15 @@ await fastify.register(FastifyVite, { spa: true, }); +fastify.get('/sentry', function (req, reply) { + return reply.send({ + // @ts-ignore + FRONTEND_SENTRY_DSN: fastify.config.FRONTEND_SENTRY_DSN, + // @ts-ignore + FRONTEND_SENTRY_ENVIRONMENT: fastify.config.FRONTEND_SENTRY_ENVIRONMENT, + }); +}); + // @ts-ignore fastify.get('/', function (req, reply) { return reply.html(); diff --git a/server/config/env.ts b/server/config/env.ts index 7f063198..1bcfe43a 100644 --- a/server/config/env.ts +++ b/server/config/env.ts @@ -30,8 +30,8 @@ const schema = { FEEDBACK_URL_LINK: { type: 'string' }, FRAME_ANCESTORS: { type: 'string' }, BFF_SENTRY_DSN: { type: 'string' }, - VITE_SENTRY_DSN: { type: 'string' }, - VITE_SENTRY_ENVIRONMENT: { type: 'string' }, + FRONTEND_SENTRY_DSN: { type: 'string' }, + FRONTEND_SENTRY_ENVIRONMENT: { type: 'string' }, // System variables NODE_ENV: { type: 'string', enum: ['development', 'production'] }, diff --git a/src/mount.ts b/src/mount.ts index 74e9e5f5..a24171d2 100644 --- a/src/mount.ts +++ b/src/mount.ts @@ -5,10 +5,18 @@ import React from 'react'; import { Routes, useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from 'react-router-dom'; let sentryRoutes = Routes; -if (import.meta.env.VITE_SENTRY_DSN && import.meta.env.VITE_SENTRY_DSN.length > 0) { + +const sentryConfig = await fetch('/sentry').then((res) => res.json()); + +if ( + sentryConfig.FRONTEND_SENTRY_DSN && + sentryConfig.FRONTEND_SENTRY_DSN.length > 0 && + sentryConfig.FRONTEND_SENTRY_ENVIRONMENT && + sentryConfig.FRONTEND_SENTRY_ENVIRONMENT.length > 0 +) { Sentry.init({ - dsn: import.meta.env.VITE_SENTRY_DSN, - environment: import.meta.env.VITE_SENTRY_ENVIRONMENT, + dsn: sentryConfig.FRONTEND_SENTRY_DSN, + environment: sentryConfig.FRONTEND_SENTRY_ENVIRONMENT, integrations: [ Sentry.reactRouterV7BrowserTracingIntegration({ useEffect: React.useEffect, diff --git a/vite.config.js b/vite.config.js index 3239d584..34b19992 100644 --- a/vite.config.js +++ b/vite.config.js @@ -14,6 +14,7 @@ export default defineConfig({ sentryVitePlugin({ org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, + telemetry: false, reactComponentAnnotation: { enabled: true, }, @@ -22,5 +23,6 @@ export default defineConfig({ build: { sourcemap: true, + target: 'esnext', // Support top-level await }, }); From df48941bcc78af108869e76fe09c6eabec1304c0 Mon Sep 17 00:00:00 2001 From: Moritz Reich Date: Fri, 5 Sep 2025 13:17:25 +0200 Subject: [PATCH 3/6] chore(deps): remove ts-node dependency from package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 67562918..dd348659 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,6 @@ "fastify-tsconfig": "^3.0.0", "globals": "^16.0.0", "prettier": "^3.5.3", - "ts-node": "^10.9.2", "tsx": "^4.20.5", "typescript": "^5.7.3", "typescript-eslint": "^8.26.1", From 04f4adc441617893305a20c86f3642b7306aeede Mon Sep 17 00:00:00 2001 From: Moritz Reich Date: Fri, 5 Sep 2025 13:21:21 +0200 Subject: [PATCH 4/6] fix(helmet): remove 'unsafe-eval' from script-src directives --- server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.ts b/server.ts index 574547dc..a0f42458 100644 --- a/server.ts +++ b/server.ts @@ -97,7 +97,7 @@ fastify.register(helmet, { 'connect-src': ["'self'", 'sdk.openui5.org', sentryHost, dynatraceOrigin], 'script-src': isLocalDev ? ["'self'", "'unsafe-inline'", "'unsafe-eval'", sentryHost, dynatraceOrigin] - : ["'self'", "'unsafe-eval'", sentryHost, dynatraceOrigin], + : ["'self'", sentryHost, dynatraceOrigin], // @ts-ignore 'frame-ancestors': [...fastify.config.FRAME_ANCESTORS.split(',')], }, From 7cb0fb2adb8a81413463795955bfec6c2c697892 Mon Sep 17 00:00:00 2001 From: Moritz Reich Date: Fri, 5 Sep 2025 13:29:07 +0200 Subject: [PATCH 5/6] refactor(sentry): moved sentry into separate file and added proper error handling --- src/lib/sentry.ts | 65 +++++++++++++++++++++++++++++++++++++++++++++++ src/mount.ts | 34 +++---------------------- 2 files changed, 69 insertions(+), 30 deletions(-) create mode 100644 src/lib/sentry.ts diff --git a/src/lib/sentry.ts b/src/lib/sentry.ts new file mode 100644 index 00000000..48d85270 --- /dev/null +++ b/src/lib/sentry.ts @@ -0,0 +1,65 @@ +import * as Sentry from '@sentry/react'; +import React from 'react'; +import { Routes, useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from 'react-router-dom'; + +// Define proper typing for Sentry configuration +interface SentryConfig { + FRONTEND_SENTRY_DSN: string; + FRONTEND_SENTRY_ENVIRONMENT: string; +} + +// Validate sentryConfig format +function isValidSentryConfig(config: unknown): config is SentryConfig { + if (typeof config !== 'object' || config === null) { + return false; + } + + const typedConfig = config as Record; + return ( + typeof typedConfig.FRONTEND_SENTRY_DSN === 'string' && + typedConfig.FRONTEND_SENTRY_DSN.length > 0 && + typeof typedConfig.FRONTEND_SENTRY_ENVIRONMENT === 'string' && + typedConfig.FRONTEND_SENTRY_ENVIRONMENT.length > 0 + ); +} + +// Fetch Sentry configuration from server +async function fetchSentryConfig(): Promise { + try { + const response = await fetch('/sentry'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.warn('Failed to fetch Sentry configuration:', error); + return null; + } +} + +// Initialize Sentry and return the wrapped Routes component +export async function initializeSentry(): Promise { + const sentryConfig = await fetchSentryConfig(); + + if (!isValidSentryConfig(sentryConfig)) { + console.warn('Invalid or missing Sentry configuration, continuing without Sentry integration'); + return Routes; + } + + // Initialize Sentry with valid configuration + Sentry.init({ + dsn: sentryConfig.FRONTEND_SENTRY_DSN, + environment: sentryConfig.FRONTEND_SENTRY_ENVIRONMENT, + integrations: [ + Sentry.reactRouterV7BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ], + }); + + return Sentry.withSentryReactRouterV7Routing(Routes); +} diff --git a/src/mount.ts b/src/mount.ts index a24171d2..481d77af 100644 --- a/src/mount.ts +++ b/src/mount.ts @@ -1,37 +1,11 @@ import { createRoot } from 'react-dom/client'; import { createApp } from './main.tsx'; -import * as Sentry from '@sentry/react'; -import React from 'react'; -import { Routes, useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from 'react-router-dom'; +import { initializeSentry } from './lib/sentry.ts'; -let sentryRoutes = Routes; +// Initialize Sentry and get the Routes component (with or without Sentry integration) +const SentryRoutes = await initializeSentry(); -const sentryConfig = await fetch('/sentry').then((res) => res.json()); - -if ( - sentryConfig.FRONTEND_SENTRY_DSN && - sentryConfig.FRONTEND_SENTRY_DSN.length > 0 && - sentryConfig.FRONTEND_SENTRY_ENVIRONMENT && - sentryConfig.FRONTEND_SENTRY_ENVIRONMENT.length > 0 -) { - Sentry.init({ - dsn: sentryConfig.FRONTEND_SENTRY_DSN, - environment: sentryConfig.FRONTEND_SENTRY_ENVIRONMENT, - integrations: [ - Sentry.reactRouterV7BrowserTracingIntegration({ - useEffect: React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - }), - ], - }); - - sentryRoutes = Sentry.withSentryReactRouterV7Routing(Routes); -} - -export const SentryRoutes = sentryRoutes; +export { SentryRoutes }; const root = createRoot(document.getElementById('root')!); root.render(createApp()); From 6da469f51905e38c33532f2e47f2666b17201b52 Mon Sep 17 00:00:00 2001 From: Moritz Reich Date: Mon, 8 Sep 2025 09:15:23 +0200 Subject: [PATCH 6/6] fix(sentry): update environment variable for Sentry initialization --- server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.ts b/server.ts index a0f42458..9c025a82 100644 --- a/server.ts +++ b/server.ts @@ -24,7 +24,7 @@ if (!process.env.BFF_SENTRY_DSN || process.env.BFF_SENTRY_DSN.trim() === '') { } else { Sentry.init({ dsn: process.env.BFF_SENTRY_DSN, - environment: process.env.VITE_SENTRY_ENVIRONMENT, + environment: process.env.FRONTEND_SENTRY_ENVIRONMENT, beforeSend(event) { if (event.request && event.request.cookies) { event.request.cookies = Object.keys(event.request.cookies).reduce((acc, key) => {