1// Alchemy modifications are licensed under Apache-2.0.
2// This file includes third-party code; see /THIRD_PARTY_LICENSES.md.
3/**
4 * Vendored from `@astrojs/cloudflare` v14.1.3 (`src/utils/cf-helpers.ts`).
5 *
6 * Pure Cloudflare helpers with no virtual module dependencies.
7 */
8import { getValidatedIpFromHeader } from "@astrojs/internal-helpers/request";
9
10export interface Runtime {
11 cfContext: ExecutionContext;
12}
13
14/** Minimal manifest shape needed by the Cloudflare helpers. */
15export interface ManifestLike {
16 assets: Set<string>;
17 sessionConfig?: { options?: Record<string, unknown> } | undefined;
18}
19
20/**
21 * Returns a `Response` from the ASSETS binding if the request pathname
22 * is a known static asset. Returns `undefined` otherwise.
23 */
24export function matchStaticAsset(
25 manifest: ManifestLike,
26 requestUrl: string,
27 env: Env,
28): Response | undefined {
29 const { pathname } = new URL(requestUrl);
30 if (manifest.assets.has(pathname)) {
31 return env.ASSETS.fetch(
32 requestUrl.replace(/\.html$/, ""),
33 ) as unknown as Response;
34 }
35 return undefined;
36}
37
38/**
39 * Tries the ASSETS binding as a fallback for an unmatched route.
40 * Returns the asset `Response` if found (non-404), `undefined` otherwise.
41 */
42export async function fallbackToAssets(
43 requestUrl: string,
44 env: Env,
45): Promise<Response | undefined> {
46 const asset = await env.ASSETS.fetch(
47 requestUrl.replace(/index.html$/, "").replace(/\.html$/, ""),
48 );
49 if (asset.status !== 404) {
50 return asset as unknown as Response;
51 }
52 return undefined;
53}
54
55/**
56 * Creates a fetch function for prerendered error pages via the ASSETS binding.
57 */
58export function createErrorPageFetch(
59 env: Env,
60): (url: string) => Promise<Response> {
61 return async (url: string) => {
62 return env.ASSETS.fetch(url.replace(/\.html$/, "")) as unknown as Response;
63 };
64}
65
66/**
67 * Creates the Cloudflare-specific locals object with `cfContext`
68 * and deprecated `runtime` property getters.
69 */
70export function createLocals(ctx: ExecutionContext): Runtime {
71 const locals: Runtime = {
72 cfContext: ctx,
73 };
74 Object.defineProperty(locals, "runtime", {
75 enumerable: false,
76 value: {
77 get env(): never {
78 throw new Error(
79 `Astro.locals.runtime.env has been removed in Astro v6. Use 'import { env } from "cloudflare:workers"' instead.`,
80 );
81 },
82 get cf(): never {
83 throw new Error(
84 `Astro.locals.runtime.cf has been removed in Astro v6. Use 'Astro.request.cf' instead.`,
85 );
86 },
87 get caches(): never {
88 throw new Error(
89 `Astro.locals.runtime.caches has been removed in Astro v6. Use the global 'caches' object instead.`,
90 );
91 },
92 get ctx(): never {
93 throw new Error(
94 `Astro.locals.runtime.ctx has been removed in Astro v6. Use 'Astro.locals.cfContext' instead.`,
95 );
96 },
97 },
98 });
99 return locals;
100}
101
102/**
103 * Extracts the client IP address from the `cf-connecting-ip` header.
104 */
105export function getClientAddress(request: Request): string | undefined {
106 return getValidatedIpFromHeader(request.headers.get("cf-connecting-ip"));
107}