| 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 | */ |
| 8 | import { getValidatedIpFromHeader } from "@astrojs/internal-helpers/request"; |
| 9 | |
| 10 | export interface Runtime { |
| 11 | cfContext: ExecutionContext; |
| 12 | } |
| 13 | |
| 14 | /** Minimal manifest shape needed by the Cloudflare helpers. */ |
| 15 | export 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 | */ |
| 24 | export 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 | */ |
| 42 | export 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 | */ |
| 58 | export 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 | */ |
| 70 | export 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 | */ |
| 105 | export function getClientAddress(request: Request): string | undefined { |
| 106 | return getValidatedIpFromHeader(request.headers.get("cf-connecting-ip")); |
| 107 | } |