Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.template
Original file line number Diff line number Diff line change
@@ -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=
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
<script type="module" src="/src/mount.ts"></script>
</body>

</html>
</html>
25 changes: 23 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -91,7 +92,7 @@
"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",
"vite": "^6.3.4",
Expand Down
21 changes: 16 additions & 5 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 = '';
}
}
Expand All @@ -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'", sentryHost, dynatraceOrigin],
// @ts-ignore
'frame-ancestors': [...fastify.config.FRAME_ANCESTORS.split(',')],
},
Expand All @@ -112,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();
Expand Down
4 changes: 2 additions & 2 deletions server/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'] },
Expand Down
65 changes: 65 additions & 0 deletions src/lib/sentry.ts
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

if (typeof config !== 'object' || config === null) {
return false;
}

const typedConfig = config as Record<string, unknown>;
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<unknown> {
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<typeof Routes> {
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);
}
26 changes: 4 additions & 22 deletions src/mount.ts
Original file line number Diff line number Diff line change
@@ -1,29 +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;
if (import.meta.env.VITE_SENTRY_DSN && import.meta.env.VITE_SENTRY_DSN.length > 0) {
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT,
integrations: [
Sentry.reactRouterV7BrowserTracingIntegration({
useEffect: React.useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes,
}),
],
});
// Initialize Sentry and get the Routes component (with or without Sentry integration)
const SentryRoutes = await initializeSentry();

sentryRoutes = Sentry.withSentryReactRouterV7Routing(Routes);
}

export const SentryRoutes = sentryRoutes;
export { SentryRoutes };

const root = createRoot(document.getElementById('root')!);
root.render(createApp());
2 changes: 2 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default defineConfig({
sentryVitePlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
telemetry: false,
reactComponentAnnotation: {
enabled: true,
},
Expand All @@ -22,5 +23,6 @@ export default defineConfig({

build: {
sourcemap: true,
target: 'esnext', // Support top-level await
},
});
Loading