Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
19 changes: 15 additions & 4 deletions server.ts
Original file line number Diff line number Diff line change
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