diff --git a/apps/web/src/api/server-functions/screenshots/browser-login.ts b/apps/web/src/api/server-functions/browser-login.ts similarity index 73% rename from apps/web/src/api/server-functions/screenshots/browser-login.ts rename to apps/web/src/api/server-functions/browser-login.ts index f7a4641a0..cfc0f2284 100644 --- a/apps/web/src/api/server-functions/screenshots/browser-login.ts +++ b/apps/web/src/api/server-functions/browser-login.ts @@ -1,19 +1,21 @@ -import { chromium } from 'playwright'; +import { type Browser, chromium, type Page } from 'playwright'; import { env } from '@/env'; import { getSupabaseServerClient } from '@/integrations/supabase/server'; -export const browserLogin = async ({ +export const browserLogin = async >({ accessToken, width, height, fullPath, request, + callback, }: { accessToken: string; width: number; height: number; fullPath: string; request: Request; + callback: ({ page, browser }: { page: Page; browser: Browser }) => Promise; }) => { const supabase = getSupabaseServerClient(); const jwtPayload = JSON.parse(Buffer.from(accessToken.split('.')[1], 'base64').toString()); @@ -63,17 +65,25 @@ export const browserLogin = async ({ const page = await context.newPage(); const fullPathWithOrigin = `${origin}${fullPath}`; + let pageError: Error | null = null; + page.on('console', (msg) => { - const hasError = msg.type() === 'error'; - if (hasError) { - browser.close(); - throw new Error(`Error in browser: ${msg.text()}`); + const text = msg.text(); + // React logs errors to console even when caught by error boundaries + if (msg.type() === 'error' && (text.includes('Error:') || text.includes('occurred in'))) { + pageError = new Error(`Page error: ${text}`); } }); await page.goto(fullPathWithOrigin, { waitUntil: 'networkidle' }); - return { context, browser, page }; + const result = await callback({ page, browser }); + + if (pageError) { + throw pageError; + } + + return { result }; } catch (error) { console.error('Error logging in to browser', error); await browser.close(); diff --git a/apps/web/src/api/server-functions/screenshot-helpers.ts b/apps/web/src/api/server-functions/screenshot-helpers.ts new file mode 100644 index 000000000..f069cccf6 --- /dev/null +++ b/apps/web/src/api/server-functions/screenshot-helpers.ts @@ -0,0 +1,13 @@ +export const createScreenshotResponse = ({ + screenshotBuffer, +}: { + screenshotBuffer: Buffer; +}) => { + if (!screenshotBuffer) { + throw new Error('Screenshot buffer is required'); + } + + return new Response(new Uint8Array(screenshotBuffer), { + headers: { 'Content-Type': 'image/png', 'Content-Length': screenshotBuffer.length.toString() }, + }); +}; diff --git a/apps/web/src/api/server-functions/screenshots/shared-schema.ts b/apps/web/src/api/server-functions/screenshots/shared-schema.ts deleted file mode 100644 index 2ed9fe8ca..000000000 --- a/apps/web/src/api/server-functions/screenshots/shared-schema.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { z } from 'zod'; - -export const GetScreenshotRequestSharedSchema = z.object({}); - -export type GetScreenshotRequestShared = z.infer; diff --git a/apps/web/src/routes/screenshots/_content/metrics.$metricId.content.tsx b/apps/web/src/routes/screenshots/_content/metrics.$metricId.content.tsx index a76972d13..41aa16e89 100644 --- a/apps/web/src/routes/screenshots/_content/metrics.$metricId.content.tsx +++ b/apps/web/src/routes/screenshots/_content/metrics.$metricId.content.tsx @@ -1,7 +1,6 @@ import { createFileRoute } from '@tanstack/react-router'; import { prefetchGetMetric } from '@/api/buster_rest/metrics'; import { useGetUserBasicInfo } from '@/api/buster_rest/users/useGetUserInfo'; -import { Route as ScreenshotsRoute } from '../_content'; import { GetMetricScreenshotQuerySchema } from '../metrics.$metricId.index'; export const Route = createFileRoute('/screenshots/_content/metrics/$metricId/content')({ @@ -14,7 +13,7 @@ export const Route = createFileRoute('/screenshots/_content/metrics/$metricId/co id: params.metricId, version_number: search.version_number, }); - if (!res || true) { + if (!res) { throw new Error('Metric not found'); } return { diff --git a/apps/web/src/routes/screenshots/metrics.$metricId.index.tsx b/apps/web/src/routes/screenshots/metrics.$metricId.index.tsx index 997267342..6060ebc3e 100644 --- a/apps/web/src/routes/screenshots/metrics.$metricId.index.tsx +++ b/apps/web/src/routes/screenshots/metrics.$metricId.index.tsx @@ -1,11 +1,10 @@ import { createServerFileRoute } from '@tanstack/react-start/server'; import { z } from 'zod'; -import { browserLogin } from '@/api/server-functions/screenshots/browser-login'; +import { browserLogin } from '@/api/server-functions/browser-login'; +import { createScreenshotResponse } from '@/api/server-functions/screenshot-helpers'; import { getSupabaseServerClient } from '@/integrations/supabase/server'; import { createHrefFromLink } from '@/lib/routes'; -const isDev = import.meta.env.DEV; - export const GetMetricScreenshotParamsSchema = z.object({ metricId: z.string(), }); @@ -36,43 +35,26 @@ export const ServerRoute = createServerFileRoute('/screenshots/metrics/$metricId Object.fromEntries(new URL(request.url).searchParams) ); - const { browser, page } = await browserLogin({ - accessToken, - width, - height, - fullPath: createHrefFromLink({ - to: '/screenshots/metrics/$metricId/content', - params: { metricId }, - search: { version_number, type, width, height }, - }), - request, - }); - try { - const screenshotBuffer = await page.screenshot({ - type, - }); - - if (!isDev) { - return new Response( - JSON.stringify({ - success: true, - }), - { - status: 200, - headers: { - 'Content-Type': 'application/json', - }, - } - ); - } - - return new Response(new Uint8Array(screenshotBuffer), { - headers: { - 'Content-Type': 'image/png', - 'Content-Length': screenshotBuffer.length.toString(), + const { result: screenshotBuffer } = await browserLogin({ + accessToken, + width, + height, + fullPath: createHrefFromLink({ + to: '/screenshots/metrics/$metricId/content', + params: { metricId }, + search: { version_number, type, width, height }, + }), + request, + callback: async ({ page }) => { + const screenshotBuffer = await page.screenshot({ + type, + }); + return screenshotBuffer; }, }); + + return createScreenshotResponse({ screenshotBuffer }); } catch (error) { console.error('Error capturing metric screenshot', error); return new Response( @@ -81,8 +63,6 @@ export const ServerRoute = createServerFileRoute('/screenshots/metrics/$metricId }), { status: 500, headers: { 'Content-Type': 'application/json' } } ); - } finally { - await browser.close(); } }, });