diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec44bc343fc1..c4122a129a8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -140,7 +140,6 @@ jobs: - 'packages/profiling-node/**' - 'dev-packages/e2e-tests/test-applications/node-profiling/**' profiling_node_bindings: - - *workflow - 'packages/profiling-node/**' - 'dev-packages/e2e-tests/test-applications/node-profiling/**' deno: @@ -888,8 +887,6 @@ jobs: remix: 1 - node: 16 remix: 1 - - tracingIntegration: true - remix: 2 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -907,7 +904,6 @@ jobs: env: NODE_VERSION: ${{ matrix.node }} REMIX_VERSION: ${{ matrix.remix }} - TRACING_INTEGRATION: ${{ matrix.tracingIntegration }} run: | cd packages/remix yarn test:integration:ci diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx index a43de2e086de..4eb7e3d3553f 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx @@ -19,6 +19,7 @@ Sentry.init({ // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. + tunnel: 'http://localhost:3031/', // proxy server }); Sentry.addEventProcessor(event => { diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx index c3deb6369af3..d228a7606ac6 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx @@ -17,6 +17,7 @@ Sentry.init({ dsn: process.env.E2E_TEST_DSN, // Performance Monitoring tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! + tunnel: 'http://localhost:3031/', // proxy server }); export const handleError = Sentry.wrapRemixHandleError; diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/_index.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/_index.tsx index 8907ef7816fd..b646c62ee4da 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/_index.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/_index.tsx @@ -1,7 +1,13 @@ -import { Link } from '@remix-run/react'; +import { Link, useSearchParams } from '@remix-run/react'; import * as Sentry from '@sentry/remix'; export default function Index() { + const [searchParams] = useSearchParams(); + + if (searchParams.get('tag')) { + Sentry.setTag('sentry_test', searchParams.get('tag')); + } + return (
{ + const eventCallbackListeners: Set<(data: string) => void> = new Set(); + + const proxyServer = http.createServer((proxyRequest, proxyResponse) => { + const proxyRequestChunks: Uint8Array[] = []; + + proxyRequest.addListener('data', (chunk: Buffer) => { + proxyRequestChunks.push(chunk); + }); + + proxyRequest.addListener('error', err => { + throw err; + }); + + proxyRequest.addListener('end', () => { + const proxyRequestBody = + proxyRequest.headers['content-encoding'] === 'gzip' + ? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString() + : Buffer.concat(proxyRequestChunks).toString(); + + let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]); + + if (!envelopeHeader.dsn) { + throw new Error('[event-proxy-server] No dsn on envelope header. Please set tunnel option.'); + } + + const { origin, pathname, host } = new URL(envelopeHeader.dsn); + + const projectId = pathname.substring(1); + const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`; + + proxyRequest.headers.host = host; + + const sentryResponseChunks: Uint8Array[] = []; + + const sentryRequest = https.request( + sentryIngestUrl, + { headers: proxyRequest.headers, method: proxyRequest.method }, + sentryResponse => { + sentryResponse.addListener('data', (chunk: Buffer) => { + proxyResponse.write(chunk, 'binary'); + sentryResponseChunks.push(chunk); + }); + + sentryResponse.addListener('end', () => { + eventCallbackListeners.forEach(listener => { + const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); + + const data: SentryRequestCallbackData = { + envelope: parseEnvelope(proxyRequestBody), + rawProxyRequestBody: proxyRequestBody, + rawSentryResponseBody, + sentryResponseStatusCode: sentryResponse.statusCode, + }; + + listener(Buffer.from(JSON.stringify(data)).toString('base64')); + }); + proxyResponse.end(); + }); + + sentryResponse.addListener('error', err => { + throw err; + }); + + proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers); + }, + ); + + sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary'); + sentryRequest.end(); + }); + }); + + const proxyServerStartupPromise = new Promise(resolve => { + proxyServer.listen(options.port, () => { + resolve(); + }); + }); + + const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => { + eventCallbackResponse.statusCode = 200; + eventCallbackResponse.setHeader('connection', 'keep-alive'); + + const callbackListener = (data: string): void => { + eventCallbackResponse.write(data.concat('\n'), 'utf8'); + }; + + eventCallbackListeners.add(callbackListener); + + eventCallbackRequest.on('close', () => { + eventCallbackListeners.delete(callbackListener); + }); + + eventCallbackRequest.on('error', () => { + eventCallbackListeners.delete(callbackListener); + }); + }); + + const eventCallbackServerStartupPromise = new Promise(resolve => { + eventCallbackServer.listen(0, () => { + const port = String((eventCallbackServer.address() as AddressInfo).port); + void registerCallbackServerPort(options.proxyServerName, port).then(resolve); + }); + }); + + await eventCallbackServerStartupPromise; + await proxyServerStartupPromise; + return; +} + +export async function waitForRequest( + proxyServerName: string, + callback: (eventData: SentryRequestCallbackData) => Promise | boolean, +): Promise { + const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName); + + return new Promise((resolve, reject) => { + const request = http.request(`http://localhost:${eventCallbackServerPort}/`, {}, response => { + let eventContents = ''; + + response.on('error', err => { + reject(err); + }); + + response.on('data', (chunk: Buffer) => { + const chunkString = chunk.toString('utf8'); + chunkString.split('').forEach(char => { + if (char === '\n') { + const eventCallbackData: SentryRequestCallbackData = JSON.parse( + Buffer.from(eventContents, 'base64').toString('utf8'), + ); + const callbackResult = callback(eventCallbackData); + if (typeof callbackResult !== 'boolean') { + callbackResult.then( + match => { + if (match) { + response.destroy(); + resolve(eventCallbackData); + } + }, + err => { + throw err; + }, + ); + } else if (callbackResult) { + response.destroy(); + resolve(eventCallbackData); + } + eventContents = ''; + } else { + eventContents = eventContents.concat(char); + } + }); + }); + }); + + request.end(); + }); +} + +export function waitForEnvelopeItem( + proxyServerName: string, + callback: (envelopeItem: EnvelopeItem) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForRequest(proxyServerName, async eventData => { + const envelopeItems = eventData.envelope[1]; + for (const envelopeItem of envelopeItems) { + if (await callback(envelopeItem)) { + resolve(envelopeItem); + return true; + } + } + return false; + }).catch(reject); + }); +} + +export function waitForError( + proxyServerName: string, + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForEnvelopeItem(proxyServerName, async envelopeItem => { + const [envelopeItemHeader, envelopeItemBody] = envelopeItem; + if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); + return true; + } + return false; + }).catch(reject); + }); +} + +export function waitForTransaction( + proxyServerName: string, + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForEnvelopeItem(proxyServerName, async envelopeItem => { + const [envelopeItemHeader, envelopeItemBody] = envelopeItem; + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); + return true; + } + return false; + }).catch(reject); + }); +} + +const TEMP_FILE_PREFIX = 'event-proxy-server-'; + +async function registerCallbackServerPort(serverName: string, port: string): Promise { + const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`); + await writeFile(tmpFilePath, port, { encoding: 'utf8' }); +} + +function retrieveCallbackServerPort(serverName: string): Promise { + const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`); + return readFile(tmpFilePath, 'utf8'); +} diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json index 17a21f6e0b5e..4310039b0638 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json @@ -27,6 +27,8 @@ "devDependencies": { "@playwright/test": "^1.36.2", "@remix-run/dev": "^2.7.2", + "@sentry/types": "latest || *", + "@sentry/utils": "latest || *", "@types/compression": "^1.7.5", "@types/express": "^4.17.20", "@types/morgan": "^1.9.9", @@ -43,7 +45,8 @@ "eslint-plugin-react-hooks": "^4.6.0", "typescript": "^5.1.6", "vite": "^5.1.0", - "vite-tsconfig-paths": "^4.2.1" + "vite-tsconfig-paths": "^4.2.1", + "ts-node": "10.9.1" }, "engines": { "node": ">=18.0.0" diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/playwright.config.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/playwright.config.ts index 9f04a7ee7896..dd495b0c9f98 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/playwright.config.ts @@ -2,6 +2,7 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; const port = 3030; +const eventProxyPort = 3031; /** * See https://playwright.dev/docs/test-configuration. @@ -34,6 +35,9 @@ const config: PlaywrightTestConfig = { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: `http://localhost:${port}`, }, /* Configure projects for major browsers */ @@ -44,15 +48,19 @@ const config: PlaywrightTestConfig = { ...devices['Desktop Chrome'], }, }, - // For now we only test Chrome! ], /* Run your local dev server before starting the tests */ - webServer: { - // This test app is testing the Vite dev server, so we need to run it before the tests. - command: `PORT=${port} pnpm dev`, - port, - }, + webServer: [ + { + command: 'pnpm ts-node --project="tsconfig.event-proxy-server.json" ./start-event-proxy.ts', + port: eventProxyPort, + }, + { + command: `PORT=${port} pnpm dev`, + port: port, + }, + ], }; export default config; diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/start-event-proxy.ts new file mode 100644 index 000000000000..e56a52190e63 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/start-event-proxy.ts @@ -0,0 +1,5 @@ +import { startEventProxyServer } from './event-proxy-server'; +startEventProxyServer({ + port: 3031, + proxyServerName: 'create-remix-app-express-vite-dev', +}); diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-server.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-server.test.ts new file mode 100644 index 000000000000..6a36f85cbfd0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-server.test.ts @@ -0,0 +1,35 @@ +import { expect, test } from '@playwright/test'; +import { uuid4 } from '@sentry/utils'; + +import { waitForTransaction } from '../event-proxy-server'; + +test('Sends two linked transactions (server & client) to Sentry', async ({ page }) => { + // We use this to identify the transactions + const testTag = uuid4(); + + // no server span here! + + const pageLoadTransactionPromise = waitForTransaction('create-remix-app-express-vite-dev', transactionEvent => { + return ( + transactionEvent.type === 'transaction' && + transactionEvent.contexts?.trace?.op === 'pageload' && + transactionEvent.tags?.['sentry_test'] === testTag + ); + }); + + page.goto(`/?tag=${testTag}`); + + const pageloadTransaction = await pageLoadTransactionPromise; + + expect(pageloadTransaction).toBeDefined(); + + const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id; + const pageLoadSpanId = pageloadTransaction.contexts?.trace?.span_id; + const pageLoadParentSpanId = pageloadTransaction.contexts?.trace?.parent_span_id; + + expect(pageloadTransaction.transaction).toBe('routes/_index'); + + expect(pageLoadTraceId).toBeDefined(); + expect(pageLoadParentSpanId).toBeUndefined(); + expect(pageLoadSpanId).toBeDefined(); +}); diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.event-proxy-server.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.event-proxy-server.json new file mode 100644 index 000000000000..bd49b1f0c16f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.event-proxy-server.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "allowJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "allowImportingTsExtensions": true + } +} diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.json index 77291a910914..0a6c9071cb90 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.json @@ -1,5 +1,6 @@ { "include": ["env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["event-proxy-server.ts", "start-event-proxy.ts"], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2022"], "isolatedModules": true, diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx index 361d2ab9935b..b3b5db3d9b3d 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx @@ -25,6 +25,7 @@ Sentry.init({ // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. + tunnel: 'http://localhost:3031/', // proxy server }); Sentry.addEventProcessor(event => { diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx index d8e63095fa43..98ab60159b70 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.server.tsx @@ -23,6 +23,7 @@ Sentry.init({ dsn: process.env.E2E_TEST_DSN, // Performance Monitoring tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! + tunnel: 'http://localhost:3031/', // proxy server }); export const handleError = Sentry.wrapRemixHandleError; diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/_index.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/_index.tsx index 8907ef7816fd..b646c62ee4da 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/_index.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/routes/_index.tsx @@ -1,7 +1,13 @@ -import { Link } from '@remix-run/react'; +import { Link, useSearchParams } from '@remix-run/react'; import * as Sentry from '@sentry/remix'; export default function Index() { + const [searchParams] = useSearchParams(); + + if (searchParams.get('tag')) { + Sentry.setTag('sentry_test', searchParams.get('tag')); + } + return (
{ + const eventCallbackListeners: Set<(data: string) => void> = new Set(); + + const proxyServer = http.createServer((proxyRequest, proxyResponse) => { + const proxyRequestChunks: Uint8Array[] = []; + + proxyRequest.addListener('data', (chunk: Buffer) => { + proxyRequestChunks.push(chunk); + }); + + proxyRequest.addListener('error', err => { + throw err; + }); + + proxyRequest.addListener('end', () => { + const proxyRequestBody = + proxyRequest.headers['content-encoding'] === 'gzip' + ? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString() + : Buffer.concat(proxyRequestChunks).toString(); + + let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]); + + if (!envelopeHeader.dsn) { + throw new Error('[event-proxy-server] No dsn on envelope header. Please set tunnel option.'); + } + + const { origin, pathname, host } = new URL(envelopeHeader.dsn); + + const projectId = pathname.substring(1); + const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`; + + proxyRequest.headers.host = host; + + const sentryResponseChunks: Uint8Array[] = []; + + const sentryRequest = https.request( + sentryIngestUrl, + { headers: proxyRequest.headers, method: proxyRequest.method }, + sentryResponse => { + sentryResponse.addListener('data', (chunk: Buffer) => { + proxyResponse.write(chunk, 'binary'); + sentryResponseChunks.push(chunk); + }); + + sentryResponse.addListener('end', () => { + eventCallbackListeners.forEach(listener => { + const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); + + const data: SentryRequestCallbackData = { + envelope: parseEnvelope(proxyRequestBody), + rawProxyRequestBody: proxyRequestBody, + rawSentryResponseBody, + sentryResponseStatusCode: sentryResponse.statusCode, + }; + + listener(Buffer.from(JSON.stringify(data)).toString('base64')); + }); + proxyResponse.end(); + }); + + sentryResponse.addListener('error', err => { + throw err; + }); + + proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers); + }, + ); + + sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary'); + sentryRequest.end(); + }); + }); + + const proxyServerStartupPromise = new Promise(resolve => { + proxyServer.listen(options.port, () => { + resolve(); + }); + }); + + const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => { + eventCallbackResponse.statusCode = 200; + eventCallbackResponse.setHeader('connection', 'keep-alive'); + + const callbackListener = (data: string): void => { + eventCallbackResponse.write(data.concat('\n'), 'utf8'); + }; + + eventCallbackListeners.add(callbackListener); + + eventCallbackRequest.on('close', () => { + eventCallbackListeners.delete(callbackListener); + }); + + eventCallbackRequest.on('error', () => { + eventCallbackListeners.delete(callbackListener); + }); + }); + + const eventCallbackServerStartupPromise = new Promise(resolve => { + eventCallbackServer.listen(0, () => { + const port = String((eventCallbackServer.address() as AddressInfo).port); + void registerCallbackServerPort(options.proxyServerName, port).then(resolve); + }); + }); + + await eventCallbackServerStartupPromise; + await proxyServerStartupPromise; + return; +} + +export async function waitForRequest( + proxyServerName: string, + callback: (eventData: SentryRequestCallbackData) => Promise | boolean, +): Promise { + const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName); + + return new Promise((resolve, reject) => { + const request = http.request(`http://localhost:${eventCallbackServerPort}/`, {}, response => { + let eventContents = ''; + + response.on('error', err => { + reject(err); + }); + + response.on('data', (chunk: Buffer) => { + const chunkString = chunk.toString('utf8'); + chunkString.split('').forEach(char => { + if (char === '\n') { + const eventCallbackData: SentryRequestCallbackData = JSON.parse( + Buffer.from(eventContents, 'base64').toString('utf8'), + ); + const callbackResult = callback(eventCallbackData); + if (typeof callbackResult !== 'boolean') { + callbackResult.then( + match => { + if (match) { + response.destroy(); + resolve(eventCallbackData); + } + }, + err => { + throw err; + }, + ); + } else if (callbackResult) { + response.destroy(); + resolve(eventCallbackData); + } + eventContents = ''; + } else { + eventContents = eventContents.concat(char); + } + }); + }); + }); + + request.end(); + }); +} + +export function waitForEnvelopeItem( + proxyServerName: string, + callback: (envelopeItem: EnvelopeItem) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForRequest(proxyServerName, async eventData => { + const envelopeItems = eventData.envelope[1]; + for (const envelopeItem of envelopeItems) { + if (await callback(envelopeItem)) { + resolve(envelopeItem); + return true; + } + } + return false; + }).catch(reject); + }); +} + +export function waitForError( + proxyServerName: string, + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForEnvelopeItem(proxyServerName, async envelopeItem => { + const [envelopeItemHeader, envelopeItemBody] = envelopeItem; + if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); + return true; + } + return false; + }).catch(reject); + }); +} + +export function waitForTransaction( + proxyServerName: string, + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForEnvelopeItem(proxyServerName, async envelopeItem => { + const [envelopeItemHeader, envelopeItemBody] = envelopeItem; + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); + return true; + } + return false; + }).catch(reject); + }); +} + +const TEMP_FILE_PREFIX = 'event-proxy-server-'; + +async function registerCallbackServerPort(serverName: string, port: string): Promise { + const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`); + await writeFile(tmpFilePath, port, { encoding: 'utf8' }); +} + +function retrieveCallbackServerPort(serverName: string): Promise { + const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`); + return readFile(tmpFilePath, 'utf8'); +} diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json index 71ff5ff39803..646bc5f21e25 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json @@ -24,10 +24,13 @@ "@playwright/test": "^1.36.2", "@remix-run/dev": "2.7.2", "@remix-run/eslint-config": "2.7.2", + "@sentry/types": "latest || *", + "@sentry/utils": "latest || *", "@types/react": "^18.0.35", "@types/react-dom": "^18.0.11", "eslint": "^8.38.0", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "ts-node": "10.9.1" }, "engines": { "node": ">=18.0.0" diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/playwright.config.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/playwright.config.ts index 79efcbc22c1a..429baa2db33f 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/playwright.config.ts @@ -2,6 +2,7 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; const port = 3030; +const eventProxyPort = 3031; /** * See https://playwright.dev/docs/test-configuration. @@ -34,6 +35,9 @@ const config: PlaywrightTestConfig = { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: `http://localhost:${port}`, }, /* Configure projects for major browsers */ @@ -44,14 +48,19 @@ const config: PlaywrightTestConfig = { ...devices['Desktop Chrome'], }, }, - // For now we only test Chrome! ], /* Run your local dev server before starting the tests */ - webServer: { - command: `PORT=${port} pnpm start`, - port, - }, + webServer: [ + { + command: 'pnpm ts-node-script start-event-proxy.ts', + port: eventProxyPort, + }, + { + command: `PORT=${port} pnpm start`, + port: port, + }, + ], }; export default config; diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/start-event-proxy.ts new file mode 100644 index 000000000000..cc810192de58 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/start-event-proxy.ts @@ -0,0 +1,5 @@ +import { startEventProxyServer } from './event-proxy-server'; +startEventProxyServer({ + port: 3031, + proxyServerName: 'create-remix-app-v2', +}); diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-server.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-server.test.ts new file mode 100644 index 000000000000..992a315af3d3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/behaviour-server.test.ts @@ -0,0 +1,50 @@ +import { expect, test } from '@playwright/test'; +import { uuid4 } from '@sentry/utils'; + +import { waitForTransaction } from '../event-proxy-server'; + +test('Sends two linked transactions (server & client) to Sentry', async ({ page }) => { + // We use this to identify the transactions + const testTag = uuid4(); + + const httpServerTransactionPromise = waitForTransaction('create-remix-app-v2', transactionEvent => { + return ( + transactionEvent.type === 'transaction' && + transactionEvent.contexts?.trace?.op === 'http.server' && + transactionEvent.tags?.['sentry_test'] === testTag + ); + }); + + const pageLoadTransactionPromise = waitForTransaction('create-remix-app-v2', transactionEvent => { + return ( + transactionEvent.type === 'transaction' && + transactionEvent.contexts?.trace?.op === 'pageload' && + transactionEvent.tags?.['sentry_test'] === testTag + ); + }); + + page.goto(`/?tag=${testTag}`); + + const pageloadTransaction = await pageLoadTransactionPromise; + const httpServerTransaction = await httpServerTransactionPromise; + + expect(pageloadTransaction).toBeDefined(); + expect(httpServerTransaction).toBeDefined(); + + const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id; + const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id; + + const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id; + const pageLoadSpanId = pageloadTransaction.contexts?.trace?.span_id; + const pageLoadParentSpanId = pageloadTransaction.contexts?.trace?.parent_span_id; + + expect(httpServerTransaction.transaction).toBe('routes/_index'); + expect(pageloadTransaction.transaction).toBe('routes/_index'); + + expect(httpServerTraceId).toBeDefined(); + expect(httpServerSpanId).toBeDefined(); + + expect(pageLoadTraceId).toEqual(httpServerTraceId); + expect(pageLoadParentSpanId).toEqual(httpServerSpanId); + expect(pageLoadSpanId).not.toEqual(httpServerSpanId); +}); diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx index 5cf3bb788d6b..93eab0f819fb 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx @@ -18,6 +18,7 @@ Sentry.init({ // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. + tunnel: 'http://localhost:3031/', // proxy server }); Sentry.addEventProcessor(event => { diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.server.tsx index c2de73cdba63..03364b7d1f32 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.server.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.server.tsx @@ -20,6 +20,7 @@ Sentry.init({ dsn: process.env.E2E_TEST_DSN, // Performance Monitoring tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! + tunnel: 'http://localhost:3031/', // proxy server }); export const handleError = Sentry.wrapRemixHandleError; diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/_index.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/_index.tsx index 8907ef7816fd..b646c62ee4da 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/_index.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/_index.tsx @@ -1,7 +1,13 @@ -import { Link } from '@remix-run/react'; +import { Link, useSearchParams } from '@remix-run/react'; import * as Sentry from '@sentry/remix'; export default function Index() { + const [searchParams] = useSearchParams(); + + if (searchParams.get('tag')) { + Sentry.setTag('sentry_test', searchParams.get('tag')); + } + return (
{ + const eventCallbackListeners: Set<(data: string) => void> = new Set(); + + const proxyServer = http.createServer((proxyRequest, proxyResponse) => { + const proxyRequestChunks: Uint8Array[] = []; + + proxyRequest.addListener('data', (chunk: Buffer) => { + proxyRequestChunks.push(chunk); + }); + + proxyRequest.addListener('error', err => { + throw err; + }); + + proxyRequest.addListener('end', () => { + const proxyRequestBody = + proxyRequest.headers['content-encoding'] === 'gzip' + ? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString() + : Buffer.concat(proxyRequestChunks).toString(); + + let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]); + + if (!envelopeHeader.dsn) { + throw new Error('[event-proxy-server] No dsn on envelope header. Please set tunnel option.'); + } + + const { origin, pathname, host } = new URL(envelopeHeader.dsn); + + const projectId = pathname.substring(1); + const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`; + + proxyRequest.headers.host = host; + + const sentryResponseChunks: Uint8Array[] = []; + + const sentryRequest = https.request( + sentryIngestUrl, + { headers: proxyRequest.headers, method: proxyRequest.method }, + sentryResponse => { + sentryResponse.addListener('data', (chunk: Buffer) => { + proxyResponse.write(chunk, 'binary'); + sentryResponseChunks.push(chunk); + }); + + sentryResponse.addListener('end', () => { + eventCallbackListeners.forEach(listener => { + const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); + + const data: SentryRequestCallbackData = { + envelope: parseEnvelope(proxyRequestBody), + rawProxyRequestBody: proxyRequestBody, + rawSentryResponseBody, + sentryResponseStatusCode: sentryResponse.statusCode, + }; + + listener(Buffer.from(JSON.stringify(data)).toString('base64')); + }); + proxyResponse.end(); + }); + + sentryResponse.addListener('error', err => { + throw err; + }); + + proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers); + }, + ); + + sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary'); + sentryRequest.end(); + }); + }); + + const proxyServerStartupPromise = new Promise(resolve => { + proxyServer.listen(options.port, () => { + resolve(); + }); + }); + + const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => { + eventCallbackResponse.statusCode = 200; + eventCallbackResponse.setHeader('connection', 'keep-alive'); + + const callbackListener = (data: string): void => { + eventCallbackResponse.write(data.concat('\n'), 'utf8'); + }; + + eventCallbackListeners.add(callbackListener); + + eventCallbackRequest.on('close', () => { + eventCallbackListeners.delete(callbackListener); + }); + + eventCallbackRequest.on('error', () => { + eventCallbackListeners.delete(callbackListener); + }); + }); + + const eventCallbackServerStartupPromise = new Promise(resolve => { + eventCallbackServer.listen(0, () => { + const port = String((eventCallbackServer.address() as AddressInfo).port); + void registerCallbackServerPort(options.proxyServerName, port).then(resolve); + }); + }); + + await eventCallbackServerStartupPromise; + await proxyServerStartupPromise; + return; +} + +export async function waitForRequest( + proxyServerName: string, + callback: (eventData: SentryRequestCallbackData) => Promise | boolean, +): Promise { + const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName); + + return new Promise((resolve, reject) => { + const request = http.request(`http://localhost:${eventCallbackServerPort}/`, {}, response => { + let eventContents = ''; + + response.on('error', err => { + reject(err); + }); + + response.on('data', (chunk: Buffer) => { + const chunkString = chunk.toString('utf8'); + chunkString.split('').forEach(char => { + if (char === '\n') { + const eventCallbackData: SentryRequestCallbackData = JSON.parse( + Buffer.from(eventContents, 'base64').toString('utf8'), + ); + const callbackResult = callback(eventCallbackData); + if (typeof callbackResult !== 'boolean') { + callbackResult.then( + match => { + if (match) { + response.destroy(); + resolve(eventCallbackData); + } + }, + err => { + throw err; + }, + ); + } else if (callbackResult) { + response.destroy(); + resolve(eventCallbackData); + } + eventContents = ''; + } else { + eventContents = eventContents.concat(char); + } + }); + }); + }); + + request.end(); + }); +} + +export function waitForEnvelopeItem( + proxyServerName: string, + callback: (envelopeItem: EnvelopeItem) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForRequest(proxyServerName, async eventData => { + const envelopeItems = eventData.envelope[1]; + for (const envelopeItem of envelopeItems) { + if (await callback(envelopeItem)) { + resolve(envelopeItem); + return true; + } + } + return false; + }).catch(reject); + }); +} + +export function waitForError( + proxyServerName: string, + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForEnvelopeItem(proxyServerName, async envelopeItem => { + const [envelopeItemHeader, envelopeItemBody] = envelopeItem; + if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); + return true; + } + return false; + }).catch(reject); + }); +} + +export function waitForTransaction( + proxyServerName: string, + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForEnvelopeItem(proxyServerName, async envelopeItem => { + const [envelopeItemHeader, envelopeItemBody] = envelopeItem; + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); + return true; + } + return false; + }).catch(reject); + }); +} + +const TEMP_FILE_PREFIX = 'event-proxy-server-'; + +async function registerCallbackServerPort(serverName: string, port: string): Promise { + const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`); + await writeFile(tmpFilePath, port, { encoding: 'utf8' }); +} + +function retrieveCallbackServerPort(serverName: string): Promise { + const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`); + return readFile(tmpFilePath, 'utf8'); +} diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app/package.json index 5e3b56b07ee4..365fd9fb0bac 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/package.json @@ -26,8 +26,11 @@ "@remix-run/eslint-config": "^1.19.3", "@types/react": "^18.0.35", "@types/react-dom": "^18.0.11", + "@sentry/types": "latest || *", + "@sentry/utils": "latest || *", "eslint": "^8.38.0", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "ts-node": "10.9.1" }, "engines": { "node": ">=14.18" diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/playwright.config.ts b/dev-packages/e2e-tests/test-applications/create-remix-app/playwright.config.ts index 785ca43321a4..429baa2db33f 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/playwright.config.ts @@ -2,6 +2,7 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; const port = 3030; +const eventProxyPort = 3031; /** * See https://playwright.dev/docs/test-configuration. @@ -34,6 +35,9 @@ const config: PlaywrightTestConfig = { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: `http://localhost:${port}`, }, /* Configure projects for major browsers */ @@ -44,26 +48,19 @@ const config: PlaywrightTestConfig = { ...devices['Desktop Chrome'], }, }, - // For now we only test Chrome! - // { - // name: 'firefox', - // use: { - // ...devices['Desktop Firefox'], - // }, - // }, - // { - // name: 'webkit', - // use: { - // ...devices['Desktop Safari'], - // }, - // }, ], /* Run your local dev server before starting the tests */ - webServer: { - command: `PORT=${port} pnpm start`, - port, - }, + webServer: [ + { + command: 'pnpm ts-node-script start-event-proxy.ts', + port: eventProxyPort, + }, + { + command: `PORT=${port} pnpm start`, + port: port, + }, + ], }; export default config; diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/create-remix-app/start-event-proxy.ts new file mode 100644 index 000000000000..93755c9d232e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/start-event-proxy.ts @@ -0,0 +1,5 @@ +import { startEventProxyServer } from './event-proxy-server'; +startEventProxyServer({ + port: 3031, + proxyServerName: 'create-remix-app', +}); diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/tests/behaviour-server.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app/tests/behaviour-server.test.ts new file mode 100644 index 000000000000..d0d737e44a69 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/tests/behaviour-server.test.ts @@ -0,0 +1,50 @@ +import { expect, test } from '@playwright/test'; +import { uuid4 } from '@sentry/utils'; + +import { waitForTransaction } from '../event-proxy-server'; + +test('Sends two linked transactions (server & client) to Sentry', async ({ page }) => { + // We use this to identify the transactions + const testTag = uuid4(); + + const httpServerTransactionPromise = waitForTransaction('create-remix-app', transactionEvent => { + return ( + transactionEvent.type === 'transaction' && + transactionEvent.contexts?.trace?.op === 'http.server' && + transactionEvent.tags?.['sentry_test'] === testTag + ); + }); + + const pageLoadTransactionPromise = waitForTransaction('create-remix-app', transactionEvent => { + return ( + transactionEvent.type === 'transaction' && + transactionEvent.contexts?.trace?.op === 'pageload' && + transactionEvent.tags?.['sentry_test'] === testTag + ); + }); + + page.goto(`/?tag=${testTag}`); + + const pageloadTransaction = await pageLoadTransactionPromise; + const httpServerTransaction = await httpServerTransactionPromise; + + expect(pageloadTransaction).toBeDefined(); + expect(httpServerTransaction).toBeDefined(); + + const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id; + const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id; + + const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id; + const pageLoadSpanId = pageloadTransaction.contexts?.trace?.span_id; + const pageLoadParentSpanId = pageloadTransaction.contexts?.trace?.parent_span_id; + + expect(httpServerTransaction.transaction).toBe('routes/_index'); + expect(pageloadTransaction.transaction).toBe('routes/_index'); + + expect(httpServerTraceId).toBeDefined(); + expect(httpServerSpanId).toBeDefined(); + + expect(pageLoadTraceId).toEqual(httpServerTraceId); + expect(pageLoadParentSpanId).toEqual(httpServerSpanId); + expect(pageLoadSpanId).not.toEqual(httpServerSpanId); +}); diff --git a/packages/remix/package.json b/packages/remix/package.json index be7820075da7..8cc0ce185345 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -85,7 +85,6 @@ "test:integration": "run-s test:integration:v1 test:integration:v2 test:integration:tracingIntegration", "test:integration:v1": "run-s test:integration:clean test:integration:prepare test:integration:client test:integration:server", "test:integration:v2": "export REMIX_VERSION=2 && run-s test:integration:v1", - "test:integration:tracingIntegration": "export TRACING_INTEGRATION=true && run-s test:integration:v2", "test:integration:ci": "run-s test:integration:clean test:integration:prepare test:integration:client:ci test:integration:server", "test:integration:prepare": "(cd test/integration && yarn install)", "test:integration:clean": "(cd test/integration && rimraf .cache node_modules build)", diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/entry.client.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/entry.client.tsx deleted file mode 100644 index 7273433127ac..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/entry.client.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { RemixBrowser, useLocation, useMatches } from '@remix-run/react'; -import * as Sentry from '@sentry/remix'; -import { useEffect } from 'react'; -import { hydrate } from 'react-dom'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - tracesSampleRate: 1, - integrations: [Sentry.browserTracingIntegration({ useEffect, useLocation, useMatches })], -}); - -hydrate(, document); diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/entry.server.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/entry.server.tsx deleted file mode 100644 index bba366801092..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/entry.server.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { EntryContext } from '@remix-run/node'; -import { RemixServer } from '@remix-run/react'; -import * as Sentry from '@sentry/remix'; -import { renderToString } from 'react-dom/server'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - tracesSampleRate: 1, - tracePropagationTargets: ['example.org'], - // Disabling to test series of envelopes deterministically. - autoSessionTracking: false, -}); - -export const handleError = Sentry.wrapRemixHandleError; - -export default function handleRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext, -) { - let markup = renderToString(); - - responseHeaders.set('Content-Type', 'text/html'); - - return new Response('' + markup, { - status: responseStatusCode, - headers: responseHeaders, - }); -} diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/root.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/root.tsx deleted file mode 100644 index 15b78b8a6325..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/root.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { LoaderFunction, V2_MetaFunction, defer, json, redirect } from '@remix-run/node'; -import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration, useRouteError } from '@remix-run/react'; -import { V2_ErrorBoundaryComponent } from '@remix-run/react/dist/routeModules'; -import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix'; - -export const ErrorBoundary: V2_ErrorBoundaryComponent = () => { - const error = useRouteError(); - - captureRemixErrorBoundaryError(error); - - return
error
; -}; - -export const meta: V2_MetaFunction = ({ data }) => [ - { charset: 'utf-8' }, - { title: 'New Remix App' }, - { name: 'viewport', content: 'width=device-width,initial-scale=1' }, - { name: 'sentry-trace', content: data.sentryTrace }, - { name: 'baggage', content: data.sentryBaggage }, -]; - -export const loader: LoaderFunction = async ({ request }) => { - const url = new URL(request.url); - const type = url.searchParams.get('type'); - - switch (type) { - case 'empty': - return {}; - case 'plain': - return { - data_one: [], - data_two: 'a string', - }; - case 'json': - return json({ data_one: [], data_two: 'a string' }, { headers: { 'Cache-Control': 'max-age=300' } }); - case 'defer': - return defer({ data_one: [], data_two: 'a string' }); - case 'null': - return null; - case 'undefined': - return undefined; - case 'throwRedirect': - throw redirect('/?type=plain'); - case 'returnRedirect': - return redirect('/?type=plain'); - case 'throwRedirectToExternal': - throw redirect('https://example.com'); - case 'returnRedirectToExternal': - return redirect('https://example.com'); - default: { - return {}; - } - } -}; - -function App() { - return ( - - - - - - - - - - - - - ); -} - -export default withSentry(App); diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/action-json-response.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/action-json-response.$id.tsx deleted file mode 100644 index 7a00bfb2bfe7..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/action-json-response.$id.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/action-json-response.$id'; -export { default } from '../../common/routes/action-json-response.$id'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-exception.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-exception.tsx deleted file mode 100644 index 1ba745d2e63d..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-exception.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/capture-exception'; -export { default } from '../../common/routes/capture-exception'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-message.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-message.tsx deleted file mode 100644 index 9dae2318cc14..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/capture-message.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/capture-message'; -export { default } from '../../common/routes/capture-message'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/error-boundary-capture.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/error-boundary-capture.$id.tsx deleted file mode 100644 index 011f92462069..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/error-boundary-capture.$id.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/error-boundary-capture.$id'; -export { default } from '../../common/routes/error-boundary-capture.$id'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/index.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/index.tsx deleted file mode 100644 index 22c086a4c2cf..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/index'; -export { default } from '../../common/routes/index'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-defer-response.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-defer-response.$id.tsx deleted file mode 100644 index 69499e594ccc..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-defer-response.$id.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/loader-defer-response.$id'; -export { default } from '../../common/routes/loader-defer-response.$id'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-json-response.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-json-response.$id.tsx deleted file mode 100644 index 7761875bdb76..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-json-response.$id.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/loader-json-response.$id'; -export { default } from '../../common/routes/loader-json-response.$id'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-throw-response.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-throw-response.$id.tsx deleted file mode 100644 index 6b9a6a85cbef..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/loader-throw-response.$id.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/loader-throw-response.$id'; -export { default } from '../../common/routes/loader-throw-response.$id'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/manual-tracing.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/manual-tracing.$id.tsx deleted file mode 100644 index a7cfebe4ed46..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/manual-tracing.$id.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/manual-tracing.$id'; -export { default } from '../../common/routes/manual-tracing.$id'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/scope-bleed.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/scope-bleed.$id.tsx deleted file mode 100644 index 5ba2376f0339..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/scope-bleed.$id.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/scope-bleed.$id'; -export { default } from '../../common/routes/scope-bleed.$id'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/server-side-unexpected-errors.$id.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/server-side-unexpected-errors.$id.tsx deleted file mode 100644 index d9571c68ddd5..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/server-side-unexpected-errors.$id.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/server-side-unexpected-errors.$id'; -export { default } from '../../common/routes/server-side-unexpected-errors.$id'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/ssr-error.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/ssr-error.tsx deleted file mode 100644 index 627f7e126871..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/ssr-error.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/ssr-error'; -export { default } from '../../common/routes/ssr-error'; diff --git a/packages/remix/test/integration/app_v2_tracingIntegration/routes/throw-redirect.tsx b/packages/remix/test/integration/app_v2_tracingIntegration/routes/throw-redirect.tsx deleted file mode 100644 index 4425f3432b58..000000000000 --- a/packages/remix/test/integration/app_v2_tracingIntegration/routes/throw-redirect.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/throw-redirect'; -export { default } from '../../common/routes/throw-redirect'; diff --git a/packages/remix/test/integration/remix.config.js b/packages/remix/test/integration/remix.config.js index 418d3690f696..b4c7ac0837b8 100644 --- a/packages/remix/test/integration/remix.config.js +++ b/packages/remix/test/integration/remix.config.js @@ -1,9 +1,8 @@ /** @type {import('@remix-run/dev').AppConfig} */ const useV2 = process.env.REMIX_VERSION === '2'; -const useBrowserTracing = process.env.TRACING_INTEGRATION === 'true'; module.exports = { - appDirectory: useBrowserTracing ? 'app_v2_tracingIntegration' : useV2 ? 'app_v2' : 'app_v1', + appDirectory: useV2 ? 'app_v2' : 'app_v1', assetsBuildDirectory: 'public/build', serverBuildPath: 'build/index.js', publicPath: '/build/',