Last active
March 11, 2026 00:01
-
-
Save 58bits/8c4de23d10181f403d1c638fad41ce29 to your computer and use it in GitHub Desktop.
Polyfill DOMParser for Cloudflare Workers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Place this in the root of your project under functions/api/contact.ts | |
| import { getWorkerLogger } from "@/lib/logger-worker"; | |
| import { DOMParser } from "@xmldom/xmldom"; | |
| import { reCaptchaAssess } from "@/lib/recaptcha/recaptcha-enterprise-assess-worker"; | |
| const log = getWorkerLogger(); | |
| export async function onRequestPost(context: any): Promise<Response> { | |
| // Polyfill DOMParser for Cloudflare Workers | |
| // We must do this before importing the AWS SDK because the SDK captures the global DOMParser at load time. | |
| // Using dynamic import ensures this polyfill runs first. | |
| if (!globalThis.DOMParser) { | |
| (globalThis as any).DOMParser = DOMParser; | |
| } | |
| if (!globalThis.Node) { | |
| (globalThis as any).Node = { | |
| ELEMENT_NODE: 1, | |
| ATTRIBUTE_NODE: 2, | |
| TEXT_NODE: 3, | |
| CDATA_SECTION_NODE: 4, | |
| ENTITY_REFERENCE_NODE: 5, | |
| ENTITY_NODE: 6, | |
| PROCESSING_INSTRUCTION_NODE: 7, | |
| COMMENT_NODE: 8, | |
| DOCUMENT_NODE: 9, | |
| DOCUMENT_TYPE_NODE: 10, | |
| DOCUMENT_FRAGMENT_NODE: 11, | |
| NOTATION_NODE: 12, | |
| }; | |
| } | |
| try { | |
| // Dynamically import AWS SDK to ensure polyfill is active | |
| const { SESClient, SendEmailCommand } = await import("@aws-sdk/client-ses"); | |
| const { request, env } = context; | |
| const formData = await request.json(); | |
| const { name, email, message, recaptchaToken, recaptchaAction } = formData; | |
| if (!name || !email || !message) { | |
| log.error({ | |
| contact: { | |
| message: "Missing required fields", | |
| name, | |
| email, | |
| }, | |
| }); | |
| return new Response(JSON.stringify({ message: "Missing required fields" }), { | |
| status: 400, | |
| headers: { "Content-Type": "application/json" }, | |
| }); | |
| } | |
| // Optionally verify reCAPTCHA Enterprise token before sending email | |
| const recaptchaEnabled = env.RECAPTCHA_ENABLED === "true"; | |
| if (recaptchaEnabled) { | |
| if (!recaptchaToken) { | |
| log.error({ | |
| contact: { | |
| message: "Missing reCAPTCHA token", | |
| name, | |
| email, | |
| }, | |
| }); | |
| return new Response(JSON.stringify({ message: "Missing reCAPTCHA token" }), { | |
| status: 400, | |
| headers: { "Content-Type": "application/json" }, | |
| }); | |
| } | |
| const ip = request.headers.get("cf-connecting-ip") ?? "0.0.0.0"; | |
| const headers = Object.fromEntries(request.headers.entries()); | |
| // log.debug({ | |
| // contact: { | |
| // message: "Starting reCAPTCHA assessment for contact form", | |
| // action: recaptchaAction || "CONTACT_FORM", | |
| // ip, | |
| // headers, | |
| // }, | |
| // }); | |
| // const result = await reCaptchaAssess( | |
| await reCaptchaAssess( | |
| recaptchaToken, | |
| recaptchaAction || "CONTACT_FORM", | |
| { | |
| RECAPTCHA_SA_JSON: env.RECAPTCHA_SA_JSON, | |
| RECAPTCHA_PROJECT_ID: env.RECAPTCHA_PROJECT_ID, | |
| RECAPTCHA_SITE_KEY: env.RECAPTCHA_SITE_KEY, | |
| RECAPTCHA_ENABLED: env.RECAPTCHA_ENABLED, | |
| }, | |
| env.RECAPTCHA_THRESHOLD ? Number(env.RECAPTCHA_THRESHOLD) : 0.5, | |
| ip, | |
| headers, | |
| ); | |
| // log.debug({ | |
| // contact: { | |
| // message: `reCAPTCHA assessment result: ${result}`, | |
| // action: recaptchaAction || "CONTACT_FORM", | |
| // ip, | |
| // headers, | |
| // }, | |
| // }); | |
| } | |
| // Initialize SES Client | |
| const sesClient = new SESClient({ | |
| region: env.AWS_REGION, | |
| credentials: { | |
| accessKeyId: env.AWS_ACCESS_KEY_ID, | |
| secretAccessKey: env.AWS_SECRET_ACCESS_KEY, | |
| }, | |
| }); | |
| const command = new SendEmailCommand({ | |
| Source: env.SES_SOURCE_EMAIL, // Must be verified in SES | |
| Destination: { | |
| ToAddresses: [env.SES_DESTINATION_EMAIL], // Where you want to receive the contact form | |
| }, | |
| Message: { | |
| Subject: { | |
| Data: 'Website Contact Form Submission', | |
| }, | |
| Body: { | |
| Text: { | |
| Data: `Name: ${name}\nEmail: ${email}\nMessage:\n${message}`, | |
| }, | |
| Html: { | |
| Data: `<p><strong>Name:</strong> ${name}</p> | |
| <p><strong>Email:</strong> ${email}</p> | |
| <p><strong>Message:</strong><br/>${message.replace(/\n/g, "<br/>")}</p>`, | |
| }, | |
| }, | |
| }, | |
| ReplyToAddresses: [email], | |
| }); | |
| const awsResponse = await sesClient.send(command); | |
| log.info({ | |
| contact: { | |
| message: "Message sent successfully via SES", | |
| name, | |
| email, | |
| requestId: awsResponse?.$metadata?.requestId, | |
| }, | |
| }); | |
| return new Response( | |
| JSON.stringify({ | |
| message: "Message sent successfully!", | |
| requestId: awsResponse?.$metadata?.requestId, | |
| }), | |
| { | |
| status: 200, | |
| headers: { "Content-Type": "application/json" }, | |
| }, | |
| ); | |
| } catch (error: any) { | |
| log.error({ | |
| contact: { | |
| message: "Error sending email", | |
| errorMessage: error?.message, | |
| code: error?.Code || error?.code, | |
| httpStatusCode: error?.$metadata?.httpStatusCode, | |
| requestId: error?.$metadata?.requestId, | |
| }, | |
| }); | |
| // Handle AWS SDK / SES errors with structured responses | |
| if (error?.$metadata?.httpStatusCode) { | |
| const status = error.$metadata.httpStatusCode; | |
| const code = error.Code || error.code; | |
| const awsMessage = | |
| code === "InvalidClientTokenId" | |
| ? "Email configuration is invalid. Please contact support." | |
| : error.message || "Email service error."; | |
| return new Response( | |
| JSON.stringify({ | |
| message: awsMessage, | |
| code, | |
| status, | |
| }), | |
| { | |
| status: status >= 400 && status < 600 ? status : 500, | |
| headers: { "Content-Type": "application/json" }, | |
| }, | |
| ); | |
| } | |
| // Fallback for non-AWS or unexpected errors | |
| log.error({ | |
| contact: { | |
| message: "Unexpected error while sending email", | |
| error: String(error), | |
| }, | |
| }); | |
| return new Response(JSON.stringify({ message: "Failed to send message." }), { | |
| status: 500, | |
| headers: { "Content-Type": "application/json" }, | |
| }); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment