Unified · Split
| @@ -1,11 +1,10 @@ | ||
| 1 | 1 | // --------------------------------------------------------------------------- |
| 2 | −// Edge concerns — the analytics/marketing/docs request middlewares that run at | |
| 3 | −// the worker edge BEFORE the app's own mcp + api dispatch. None of these touch | |
| 4 | −// the Effect app layer; they proxy or tunnel to external services (the | |
| 5 | −// marketing worker, Sentry, PostHog, Mintlify docs). | |
| 2 | +// Edge concerns — request middleware that runs before the app's own mcp + api | |
| 3 | +// dispatch. These proxy or tunnel to external services without touching the | |
| 4 | +// Effect app layer. Marketing is dispatched even earlier, from server.ts, so a | |
| 5 | +// public page never loads the TanStack Start graph. | |
| 6 | 6 | // --------------------------------------------------------------------------- |
| 7 | 7 | |
| 8 | −export { marketingMiddleware } from "./marketing"; | |
| 9 | 8 | export { sentryTunnelMiddleware } from "./sentry-tunnel"; |
| 10 | 9 | export { posthogProxyMiddleware } from "./posthog"; |
| 11 | 10 | export { docsProxyMiddleware } from "./docs"; |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import { describe, expect, it } from "@effect/vitest"; |
| 2 | 2 | |
| 3 | −import { isMarketingPath } from "./marketing"; | |
| 3 | +import { isMarketingPath, marketingProxyRequest } from "./marketing"; | |
| 4 | 4 | |
| 5 | 5 | // On executor.sh the marketing middleware proxies an allow-list of paths to the |
| 6 | 6 | // `executor-marketing` worker; everything else falls through to the auth-gated |
| @@ -38,3 +38,54 @@ describe("isMarketingPath", () => { · 31 unmodified lines | ||
| 38 | 38 | }); |
| 39 | 39 | } |
| 40 | 40 | }); |
| 41 | + | |
| 42 | +describe("marketingProxyRequest", () => { | |
| 43 | + it("routes a signed-out homepage request", () => { | |
| 44 | + const request = new Request("https://executor.sh/?source=test"); | |
| 45 | + | |
| 46 | + const proxied = marketingProxyRequest(request); | |
| 47 | + | |
| 48 | + expect(proxied?.url).toBe("https://executor.sh/?source=test"); | |
| 49 | + }); | |
| 50 | + | |
| 51 | + it("leaves the signed-in homepage with the cloud application", () => { | |
| 52 | + const request = new Request("https://executor.sh/", { | |
| 53 | + headers: { cookie: "other=value; wos-session=sealed" }, | |
| 54 | + }); | |
| 55 | + | |
| 56 | + expect(marketingProxyRequest(request)).toBeNull(); | |
| 57 | + }); | |
| 58 | + | |
| 59 | + it("routes public content even when a session cookie is present", () => { | |
| 60 | + const request = new Request("https://executor.sh/blog/post", { | |
| 61 | + headers: { cookie: "wos-session=sealed" }, | |
| 62 | + }); | |
| 63 | + | |
| 64 | + expect(marketingProxyRequest(request)?.url).toBe("https://executor.sh/blog/post"); | |
| 65 | + }); | |
| 66 | + | |
| 67 | + it("rewrites the public home alias to the marketing root", () => { | |
| 68 | + const request = new Request("https://executor.sh/home?source=test"); | |
| 69 | + | |
| 70 | + expect(marketingProxyRequest(request)?.url).toBe("https://executor.sh/?source=test"); | |
| 71 | + }); | |
| 72 | + | |
| 73 | + it("preserves the request method, headers, and body", async () => { | |
| 74 | + const request = new Request("https://executor.sh/_astro/_ph/capture", { | |
| 75 | + method: "POST", | |
| 76 | + headers: { "content-type": "application/json", "x-request-id": "request-1" }, | |
| 77 | + body: JSON.stringify({ event: "test" }), | |
| 78 | + }); | |
| 79 | + | |
| 80 | + const proxied = marketingProxyRequest(request); | |
| 81 | + | |
| 82 | + expect(proxied?.method).toBe("POST"); | |
| 83 | + expect(proxied?.headers.get("x-request-id")).toBe("request-1"); | |
| 84 | + await expect(proxied?.json()).resolves.toEqual({ event: "test" }); | |
| 85 | + }); | |
| 86 | + | |
| 87 | + it("does not proxy non-production hosts or app-owned paths", () => { | |
| 88 | + expect(marketingProxyRequest(new Request("http://executor-cloud.localhost/"))).toBeNull(); | |
| 89 | + expect(marketingProxyRequest(new Request("https://executor.sh/login"))).toBeNull(); | |
| 90 | + }); | |
| 91 | +}); | |
| @@ -3,14 +3,10 @@ · 2 unmodified lines | ||
| 3 | 3 | // |
| 4 | 4 | // On the production domain (`executor.sh`), marketing paths and the |
| 5 | 5 | // unauthenticated landing page are served by the separate `executor-marketing` |
| 6 | −// worker (bound as `env.MARKETING`). In local dev that worker isn't running, so | |
| 7 | −// unauthenticated visits fall through to the cloud app's routes (the sign-in | |
| 8 | −// page). | |
| 6 | +// worker. This module deliberately has no TanStack Start or cloud application | |
| 7 | +// imports: the Worker entry calls it before loading the Start server graph. | |
| 9 | 8 | // --------------------------------------------------------------------------- |
| 10 | 9 | |
| 11 | −import { env } from "cloudflare:workers"; | |
| 12 | −import { createMiddleware } from "@tanstack/react-start"; | |
| 13 | − | |
| 14 | 10 | import { parseCookie } from "../auth/cookies"; |
| 15 | 11 | |
| 16 | 12 | const MARKETING_PATHS = [ |
| @@ -28,33 +24,25 @@ const MARKETING_PATHS = [ · 11 unmodified lines | ||
| 28 | 24 | "/pattern-graph-paper.svg", |
| 29 | 25 | ]; |
| 30 | 26 | |
| 31 | −export const isMarketingPath = (pathname: string) => | |
| 32 | − MARKETING_PATHS.some((p) => pathname === p || pathname.startsWith(`${p}/`)); | |
| 33 | − | |
| 34 | −const getMarketingWorker = () => env.MARKETING as { fetch: typeof fetch } | undefined; | |
| 35 | − | |
| 36 | −export const marketingMiddleware = createMiddleware({ type: "request" }).server( | |
| 37 | − async ({ pathname, request, next }) => { | |
| 38 | − // Only proxy to the marketing worker on the production domain. In local | |
| 39 | − // dev we don't run `executor-marketing`, so unauthenticated visits fall | |
| 40 | − // through to the cloud app's routes (which show the sign-in page). | |
| 41 | − const host = new URL(request.url).hostname; | |
| 42 | − if (host !== "executor.sh") return next(); | |
| 27 | +const SESSION_COOKIE = "wos-session"; | |
| 43 | 28 | |
| 44 | − const shouldProxyToMarketing = | |
| 45 | − isMarketingPath(pathname) || | |
| 46 | − (pathname === "/" && !parseCookie(request.headers.get("cookie"), "wos-session")); | |
| 47 | − | |
| 48 | − if (!shouldProxyToMarketing) return next(); | |
| 49 | − | |
| 50 | − const marketing = getMarketingWorker(); | |
| 51 | − if (!marketing) return next(); | |
| 29 | +/** Whether an exact pathname belongs to the public marketing worker. */ | |
| 30 | +export const isMarketingPath = (pathname: string): boolean => | |
| 31 | + MARKETING_PATHS.some((p) => pathname === p || pathname.startsWith(`${p}/`)); | |
| 52 | 32 | |
| 53 | − const url = new URL(request.url); | |
| 54 | − // Rewrite /home to / so marketing worker serves its homepage | |
| 55 | − if (pathname === "/home") { | |
| 56 | − url.pathname = "/"; | |
| 57 | − } | |
| 58 | − return marketing.fetch(new Request(url, request)); | |
| 59 | − }, | |
| 60 | −); | |
| 33 | +/** | |
| 34 | + * Project a production request onto the marketing service-binding request. | |
| 35 | + * Returns `null` when the cloud application owns the request instead. | |
| 36 | + */ | |
| 37 | +export const marketingProxyRequest = (request: Request): Request | null => { | |
| 38 | + const url = new URL(request.url); | |
| 39 | + if (url.hostname !== "executor.sh") return null; | |
| 40 | + | |
| 41 | + const shouldProxy = | |
| 42 | + isMarketingPath(url.pathname) || | |
| 43 | + (url.pathname === "/" && !parseCookie(request.headers.get("cookie"), SESSION_COOKIE)); | |
| 44 | + if (!shouldProxy) return null; | |
| 45 | + | |
| 46 | + if (url.pathname === "/home") url.pathname = "/"; | |
| 47 | + return new Request(url, request); | |
| 48 | +}; | |
| @@ -12,6 +12,7 @@ import * as Sentry from "@sentry/cloudflare"; · 11 unmodified lines | ||
| 12 | 12 | import handler from "@tanstack/react-start/server-entry"; |
| 13 | 13 | |
| 14 | 14 | import { isAppOwnedPath } from "./app-paths"; |
| 15 | +import { marketingProxyRequest } from "./edge/marketing"; | |
| 15 | 16 | import { makeCloudMcpAgentHandler } from "./mcp/agent-handler"; |
| 16 | 17 | import { classifyMcpPath, prepareMcpOrgScope } from "./mcp/mount"; |
| 17 | 18 | import { parseTraceparent } from "./mcp/traceparent"; |
| @@ -175,6 +176,14 @@ const mcpAgentHandler = makeCloudMcpAgentHandler({ · 157 unmodified lines | ||
| 175 | 176 | |
| 176 | 177 | const cloudflareHandler: ExportedHandler<Env> = { |
| 177 | 178 | fetch: async (request, env, ctx) => { |
| 179 | + // Public pages must not enter TanStack Start: its first-request dynamic | |
| 180 | + // import loads the entire React + Effect server graph and can take seconds | |
| 181 | + // on a cold isolate. Classify and service-bind marketing at the Worker | |
| 182 | + // entry, before telemetry or fetchHandler touches that graph. | |
| 183 | + const marketingRequest = marketingProxyRequest(request); | |
| 184 | + const marketing: Fetcher | undefined = env.MARKETING; | |
| 185 | + if (marketingRequest && marketing) return marketing.fetch(marketingRequest); | |
| 186 | + | |
| 178 | 187 | // Browser OTLP ingress — before the server span opens: exporter traffic |
| 179 | 188 | // must never trace itself (the browser already excludes /v1/traces from |
| 180 | 189 | // its own tracing for the same reason). |
| @@ -10,7 +10,6 @@ import { loginPath } from "./auth/return-to"; · 9 unmodified lines | ||
| 10 | 10 | import { prepareMcpOrgScope } from "./mcp/mount"; |
| 11 | 11 | import { |
| 12 | 12 | docsProxyMiddleware, |
| 13 | − marketingMiddleware, | |
| 14 | 13 | openAiAppsChallengeMiddleware, |
| 15 | 14 | posthogProxyMiddleware, |
| 16 | 15 | sentryTunnelMiddleware, |
| @@ -89,20 +88,19 @@ const appRequestMiddleware = createMiddleware({ type: "request" }).server( · 72 unmodified lines | ||
| 89 | 88 | }, |
| 90 | 89 | ); |
| 91 | 90 | |
| 92 | −// The edge concerns (marketing proxy, docs proxy, sentry tunnel, posthog proxy) | |
| 93 | −// live in `./edge`; they run before the app's own dispatch. Ordering is | |
| 94 | −// load-bearing: marketing first (production landing/page proxy), then the docs | |
| 95 | −// proxy and analytics tunnels, then the unified app plane (api + mcp), and last | |
| 96 | −// the SSR auth gate — it only sees document requests nothing above claimed, so | |
| 97 | −// signed-out visitors are redirected to /login before the SPA (and its | |
| 98 | −// app-shell skeleton) is served. The docs proxy sits among the edges (not after | |
| 99 | −// the auth gate) because `/docs` is public and must skip the sign-in redirect; | |
| 100 | −// its path is disjoint from every other matcher, so its slot is not otherwise | |
| 101 | −// load-bearing. | |
| 91 | +// The remaining edge concerns (docs proxy, sentry tunnel, posthog proxy) live | |
| 92 | +// in `./edge`; they run before the app's own dispatch. Marketing is handled in | |
| 93 | +// server.ts before this module is loaded. Ordering here is load-bearing: public | |
| 94 | +// challenges and docs, then analytics tunnels, then the unified app plane (api | |
| 95 | +// + mcp), and last the SSR auth gate — it only sees document requests nothing | |
| 96 | +// above claimed, so signed-out visitors are redirected to /login before the SPA | |
| 97 | +// (and its app-shell skeleton) is served. The docs proxy sits among the edges | |
| 98 | +// (not after the auth gate) because `/docs` is public and must skip the sign-in | |
| 99 | +// redirect; its path is disjoint from every other matcher, so its slot is not | |
| 100 | +// otherwise load-bearing. | |
| 102 | 101 | export const startInstance = createStart(() => ({ |
| 103 | 102 | requestMiddleware: [ |
| 104 | 103 | openAiAppsChallengeMiddleware, |
| 105 | − marketingMiddleware, | |
| 106 | 104 | docsProxyMiddleware, |
| 107 | 105 | sentryTunnelMiddleware, |
| 108 | 106 | posthogProxyMiddleware, |