diff options
Diffstat (limited to 'packages/integrations/cloudflare/src/entrypoints/server.ts')
-rw-r--r-- | packages/integrations/cloudflare/src/entrypoints/server.ts | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/packages/integrations/cloudflare/src/entrypoints/server.ts b/packages/integrations/cloudflare/src/entrypoints/server.ts new file mode 100644 index 000000000..a37f820ab --- /dev/null +++ b/packages/integrations/cloudflare/src/entrypoints/server.ts @@ -0,0 +1,112 @@ +import { env as globalEnv } from 'cloudflare:workers'; +import type { + CacheStorage as CLOUDFLARE_CACHESTORAGE, + Request as CLOUDFLARE_REQUEST, + ExecutionContext, +} from '@cloudflare/workers-types'; +import type { SSRManifest } from 'astro'; +import { App } from 'astro/app'; +import { setGetEnv } from 'astro/env/setup'; +import { createGetEnv } from '../utils/env.js'; + +setGetEnv(createGetEnv(globalEnv as Env)); + +type Env = { + [key: string]: unknown; + ASSETS: { fetch: (req: Request | string) => Promise<Response> }; + ASTRO_STUDIO_APP_TOKEN?: string; +}; + +export interface Runtime<T extends object = object> { + runtime: { + env: Env & T; + cf: CLOUDFLARE_REQUEST['cf']; + caches: CLOUDFLARE_CACHESTORAGE; + ctx: ExecutionContext; + }; +} + +declare global { + // This is not a real global, but is injected using Vite define to allow us to specify the session binding name in the config. + // eslint-disable-next-line no-var + var __ASTRO_SESSION_BINDING_NAME: string; + + // Just used to pass the KV binding to unstorage. + // eslint-disable-next-line no-var + var __env__: Partial<Env>; +} + +export function createExports(manifest: SSRManifest) { + const app = new App(manifest); + + const fetch = async ( + request: Request & CLOUDFLARE_REQUEST, + env: Env, + context: ExecutionContext, + ) => { + const { pathname } = new URL(request.url); + const bindingName = globalThis.__ASTRO_SESSION_BINDING_NAME; + // Assigning the KV binding to globalThis allows unstorage to access it for session storage. + // unstorage checks in globalThis and globalThis.__env__ for the binding. + globalThis.__env__ ??= {}; + globalThis.__env__[bindingName] = env[bindingName]; + + // static assets fallback, in case default _routes.json is not used + if (manifest.assets.has(pathname)) { + return env.ASSETS.fetch(request.url.replace(/\.html$/, '')); + } + + const routeData = app.match(request); + if (!routeData) { + // https://developers.cloudflare.com/pages/functions/api-reference/#envassetsfetch + const asset = await env.ASSETS.fetch( + request.url.replace(/index.html$/, '').replace(/\.html$/, ''), + ); + if (asset.status !== 404) { + return asset; + } + } + + Reflect.set( + request, + Symbol.for('astro.clientAddress'), + request.headers.get('cf-connecting-ip'), + ); + + process.env.ASTRO_STUDIO_APP_TOKEN ??= (() => { + if (typeof env.ASTRO_STUDIO_APP_TOKEN === 'string') { + return env.ASTRO_STUDIO_APP_TOKEN; + } + })(); + + const locals: Runtime = { + runtime: { + env: env, + cf: request.cf, + caches: caches as unknown as CLOUDFLARE_CACHESTORAGE, + ctx: { + waitUntil: (promise: Promise<any>) => context.waitUntil(promise), + // Currently not available: https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions + passThroughOnException: () => { + throw new Error( + '`passThroughOnException` is currently not available in Cloudflare Pages. See https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions.', + ); + }, + props: {}, + }, + }, + }; + + const response = await app.render(request, { routeData, locals }); + + if (app.setCookieHeaders) { + for (const setCookieHeader of app.setCookieHeaders(response)) { + response.headers.append('Set-Cookie', setCookieHeader); + } + } + + return response; + }; + + return { default: { fetch } }; +} |