import { createReadableStreamFromReadable, type EntryContext } from "@remix-run/node"; // or cloudflare/deno import { RemixServer } from "@remix-run/react"; import * as Sentry from "@sentry/remix"; import { wrapHandleErrorWithSentry } from "@sentry/remix"; import { addTenantContextToEvent } from "~/utils/sentryTenantContext.server"; import { parseAcceptLanguage } from "intl-parse-accept-language"; import isbot from "isbot"; import { renderToPipeableStream } from "react-dom/server"; import { PassThrough } from "stream"; import * as Worker from "~/services/worker.server"; import { initMollifierDrainerWorker } from "~/v3/mollifierDrainerWorker.server"; import { initMollifierStaleSweepWorker } from "~/v3/mollifierStaleSweepWorker.server"; import { initBillingLimitWorker } from "~/v3/billingLimitWorker.server"; import { bootstrap } from "./bootstrap"; import { LocaleContextProvider } from "./components/primitives/LocaleProvider"; import type { OperatingSystemPlatform } from "./components/primitives/OperatingSystemProvider"; import { OperatingSystemContextProvider } from "./components/primitives/OperatingSystemProvider"; import { assertRunOpsSplitSentinel, Prisma } from "./db.server"; import { env } from "./env.server"; import { eventLoopMonitor } from "./eventLoopMonitor.server"; import { logger } from "./services/logger.server"; import { resourceMonitor } from "./services/resourceMonitor.server"; import { singleton } from "./utils/singleton"; import { remoteBuildsEnabled } from "./v3/remoteImageBuilder.server"; import { registerRunEngineEventBusHandlers, setupBatchQueueCallbacks, } from "./v3/runEngineHandlers.server"; import { registerRunChangeNotifierHandlers } from "./services/realtime/runChangeNotifierHandlers.server"; // Touch the sessions replication singleton at entry so it boots deterministically // on webapp startup. The singleton's initializer wires start (gated on // `clickhouseFactory.isReady()`) and SIGTERM/SIGINT shutdown — mirrors // runsReplicationInstance. // // IMPORTANT: do NOT replace this with `void sessionsReplicationInstance;`. // `apps/webapp/package.json` declares `"sideEffects": false`, so esbuild // treats `void ;` as a pure expression statement and tree-shakes // the entire import — the singleton's initializer never fires and the // sessions→ClickHouse logical replication slot stops being consumed. Assigning // to globalThis is an unambiguous side effect the bundler must preserve. See // TRI-9864 for the incident write-up. import { sessionsReplicationInstance } from "./services/sessionsReplicationInstance.server"; (globalThis as Record).__sessionsReplicationInstance = sessionsReplicationInstance; import { globalFlagsRegistry } from "./v3/globalFlagsRegistry.server"; (globalThis as Record).__globalFlagsRegistry = globalFlagsRegistry; import { workerRegionRegistry } from "./v3/workerRegions.server"; (globalThis as Record).__workerRegionRegistry = workerRegionRegistry; const ABORT_DELAY = 30000; export default function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext ) { const url = new URL(request.url); if (url.pathname.startsWith("/login")) { responseHeaders.set("X-Frame-Options", "SAMEORIGIN"); responseHeaders.set("Content-Security-Policy", "frame-ancestors 'self'"); } const acceptLanguage = request.headers.get("accept-language"); const locales = parseAcceptLanguage(acceptLanguage, { validate: Intl.DateTimeFormat.supportedLocalesOf, }); //get whether it's a mac or pc from the headers const platform: OperatingSystemPlatform = request.headers.get("user-agent")?.includes("Mac") ? "mac" : "windows"; // If the request is from a bot, we want to wait for the full // response to render before sending it to the client. This // ensures that bots can see the full page content. if (isbot(request.headers.get("user-agent"))) { return handleBotRequest( request, responseStatusCode, responseHeaders, remixContext, locales, platform ); } return handleBrowserRequest( request, responseStatusCode, responseHeaders, remixContext, locales, platform ); } function handleBotRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, locales: string[], platform: OperatingSystemPlatform ) { return new Promise((resolve, reject) => { let shellRendered = false; // Timer handle is cleared in every terminal callback so the abort closure // (which captures the full React render tree + remixContext) doesn't pin // memory for 30s per successful request. See react-router PR #14200. let abortTimer: NodeJS.Timeout | undefined; const { pipe, abort } = renderToPipeableStream( , , { onAllReady() { shellRendered = true; const body = new PassThrough(); const stream = createReadableStreamFromReadable(body); responseHeaders.set("Content-Type", "text/html"); resolve( new Response(stream, { headers: responseHeaders, status: responseStatusCode, }) ); pipe(body); clearTimeout(abortTimer); }, onShellError(error: unknown) { clearTimeout(abortTimer); reject(error); }, onError(error: unknown) { responseStatusCode = 500; // Log streaming rendering errors from inside the shell. Don't log // errors encountered during initial shell rendering since they'll // reject and get logged in handleDocumentRequest. if (shellRendered) { console.error(error); } }, } ); abortTimer = setTimeout(abort, ABORT_DELAY); }); } function handleBrowserRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, locales: string[], platform: OperatingSystemPlatform ) { return new Promise((resolve, reject) => { let shellRendered = false; // Timer handle is cleared in every terminal callback so the abort closure // (which captures the full React render tree + remixContext) doesn't pin // memory for 30s per successful request. See react-router PR #14200. let abortTimer: NodeJS.Timeout | undefined; const { pipe, abort } = renderToPipeableStream( , { onShellReady() { shellRendered = true; const body = new PassThrough(); const stream = createReadableStreamFromReadable(body); responseHeaders.set("Content-Type", "text/html"); resolve( new Response(stream, { headers: responseHeaders, status: responseStatusCode, }) ); pipe(body); clearTimeout(abortTimer); }, onShellError(error: unknown) { clearTimeout(abortTimer); reject(error); }, onError(error: unknown) { responseStatusCode = 500; // Log streaming rendering errors from inside the shell. Don't log // errors encountered during initial shell rendering since they'll // reject and get logged in handleDocumentRequest. if (shellRendered) { console.error(error); } }, } ); abortTimer = setTimeout(abort, ABORT_DELAY); }); } export const handleError = wrapHandleErrorWithSentry((error, { request }) => { if (request instanceof Request) { logger.debug("Error in handleError", { error, request: { url: request.url, method: request.method, }, }); } else { logger.debug("Error in handleError", { error, }); } }); Worker.init().catch((error) => { logError(error); }); initMollifierDrainerWorker(); initMollifierStaleSweepWorker(); initBillingLimitWorker(); bootstrap().catch((error) => { logError(error); }); function logError(error: unknown, request?: Request) { console.error(error); if (error instanceof Error && error.message.startsWith("There are locked jobs present")) { console.log("⚠️ graphile-worker migration issue detected!"); } } process.on("uncaughtException", (error, origin) => { if ( error instanceof Prisma.PrismaClientKnownRequestError || error instanceof Prisma.PrismaClientUnknownRequestError ) { // Don't exit the process if the error is a Prisma error logger.error("uncaughtException prisma error", { error, prismaMessage: error.message, code: "code" in error ? error.code : undefined, meta: "meta" in error ? error.meta : undefined, stack: error.stack, origin, }); } else { logger.error("uncaughtException", { error: { name: error.name, message: error.message, stack: error.stack }, origin, }); } process.exit(1); }); // Boot-time run-ops split interlock. Async, so it runs as a // fire-and-forget at startup; a flag-on-but-sentinel-fails misconfig crashes // the process loudly before any run-ops routing is wired. singleton("AssertRunOpsSplitSentinel", () => { assertRunOpsSplitSentinel().catch((error) => { logger.error("Run-ops split sentinel assertion failed; refusing to start", { error }); process.exit(1); }); return true; }); singleton("RunEngineEventBusHandlers", registerRunEngineEventBusHandlers); singleton("SetupBatchQueueCallbacks", setupBatchQueueCallbacks); // Attach the realtime run-changed publish delegations to the engine event bus. // No-ops (registers nothing) unless REALTIME_BACKEND_NATIVE_ENABLED=1. singleton("RunChangeNotifierHandlers", registerRunChangeNotifierHandlers); // Wrapped in singleton() so Remix's dev-mode CJS reloads don't append // duplicate copies of the processor — Sentry's processor list lives in // node_modules and persists across module reloads. Idempotent at runtime // (the processor is a pure read+stamp), but the pattern matches the rest // of this file. singleton("SentryTenantContextProcessor", () => { if (env.SENTRY_DSN) { Sentry.addEventProcessor(addTenantContextToEvent); } // Return a truthy value — `singleton()` uses `??=` so a `void` // callback would re-execute (and re-register) on every dev reload. return true; }); export { apiRateLimiter } from "./services/apiRateLimit.server"; export { engineRateLimiter } from "./services/engineRateLimit.server"; export { runWithHttpContext } from "./services/httpAsyncStorage.server"; export { tenantContextMiddleware } from "./services/tenantContextResolver.server"; export { socketIo } from "./v3/handleSocketIo.server"; export { wss } from "./v3/handleWebsockets.server"; if (env.EVENT_LOOP_MONITOR_ENABLED === "1") { eventLoopMonitor.enable(); } if (remoteBuildsEnabled()) { console.log("🏗️ Remote builds enabled"); } else { console.log("🏗️ Local builds enabled"); } if (env.RESOURCE_MONITOR_ENABLED === "1") { resourceMonitor.startMonitoring(1000); }