From b7a5d49bc153073d670513bb03c6463f5fa2eb5c Mon Sep 17 00:00:00 2001 From: istarkov Date: Mon, 13 May 2024 18:35:21 +0300 Subject: [PATCH 1/5] Add simple bot protection --- .../defaults/app/route-templates/html.tsx | 116 ++++++++++-------- packages/form-handlers/src/index.ts | 2 +- packages/form-handlers/src/n8n.ts | 4 +- packages/form-handlers/src/shared.ts | 10 +- .../src/webhook-form.tsx | 67 +++++++++- 5 files changed, 137 insertions(+), 62 deletions(-) diff --git a/packages/cli/templates/defaults/app/route-templates/html.tsx b/packages/cli/templates/defaults/app/route-templates/html.tsx index 2a3279259999..ec441515a323 100644 --- a/packages/cli/templates/defaults/app/route-templates/html.tsx +++ b/packages/cli/templates/defaults/app/route-templates/html.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/packages/form-handlers/src/index.ts b/packages/form-handlers/src/index.ts index 27190e8eef42..448db2f464f0 100644 --- a/packages/form-handlers/src/index.ts +++ b/packages/form-handlers/src/index.ts @@ -1,2 +1,2 @@ -export { formIdFieldName, getFormId } from "./shared"; +export { formIdFieldName, formBotFieldName } from "./shared"; export { n8nHandler } from "./n8n"; diff --git a/packages/form-handlers/src/n8n.ts b/packages/form-handlers/src/n8n.ts index 18c73cb66f0d..22fa52e972db 100644 --- a/packages/form-handlers/src/n8n.ts +++ b/packages/form-handlers/src/n8n.ts @@ -5,7 +5,7 @@ import { getFormEntries, getErrors, getResponseBody, - getFormId, + formIdFieldName, } from "./shared"; const getAuth = (hookUrl: string) => { @@ -40,7 +40,7 @@ export const n8nHandler = async ({ headers["Authorization"] = `Basic ${btoa([username, password].join(":"))}`; } - const formId = getFormId(formInfo.formData); + const formId = formInfo.formData.get(formIdFieldName); if (formId === undefined) { return { success: false, errors: ["No form id in FormData"] }; diff --git a/packages/form-handlers/src/shared.ts b/packages/form-handlers/src/shared.ts index a4de2bb069c9..9742deca72b1 100644 --- a/packages/form-handlers/src/shared.ts +++ b/packages/form-handlers/src/shared.ts @@ -1,5 +1,7 @@ const formHiddenFieldPrefix = "ws--form"; export const formIdFieldName = `${formHiddenFieldPrefix}-id`; +// Used for simlpe protection against non js bots +export const formBotFieldName = `${formHiddenFieldPrefix}-bot`; // Input data common for all handlers export type FormInfo = { @@ -32,14 +34,6 @@ export const getFormEntries = (formData: FormData): [string, string][] => : [] ); -export const getFormId = (formData: FormData) => { - for (const [key, value] of formData.entries()) { - if (key === formIdFieldName && typeof value === "string") { - return value; - } - } -}; - const getDomain = (url: string) => { try { return new URL(url).hostname; diff --git a/packages/sdk-components-react-remix/src/webhook-form.tsx b/packages/sdk-components-react-remix/src/webhook-form.tsx index 96f648fe6159..addb75863d30 100644 --- a/packages/sdk-components-react-remix/src/webhook-form.tsx +++ b/packages/sdk-components-react-remix/src/webhook-form.tsx @@ -8,6 +8,7 @@ import { import { useFetcher, type Fetcher, type FormProps } from "@remix-run/react"; import { formIdFieldName } from "@webstudio-is/form-handlers"; import { getInstanceIdFromComponentProps } from "@webstudio-is/react-sdk"; +import { formBotFieldName } from "../../form-handlers/src/shared"; export const defaultTag = "form"; @@ -33,6 +34,50 @@ const useOnFetchEnd = ( type State = "initial" | "success" | "error"; +// gcd - greatest common divisor +const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b)); + +const getAspectRatioString = (width: number, height: number) => { + const r = gcd(width, height); + const aspectRatio = `${width / r}/${height / r}`; + return aspectRatio; +}; + +/** + * jsdom detector, trying to check that matchMedia is working (jsdom has no support of matchMedia and usually simple stub is used) + */ +const isJSDom = () => { + if (typeof matchMedia === "undefined") { + return true; + } + + const { width, height } = screen; + const deviceAspectRatio = getAspectRatioString(width, height); + + const matchAspectRatio = matchMedia( + `(device-aspect-ratio: ${deviceAspectRatio})` + ).matches; + + const matchWidthHeight = matchMedia( + `(device-width: ${width}px) and (device-height: ${height}px)` + ).matches; + + const matchWidthHeightFail = matchMedia( + `(device-width: ${width - 1}px) and (device-height: ${height}px)` + ).matches; + + const matchLight = matchMedia("(prefers-color-scheme: light)").matches; + const matchDark = matchMedia("(prefers-color-scheme: dark)").matches; + + const hasMatchMedia = + matchAspectRatio && + matchWidthHeight && + !matchWidthHeightFail && + matchLight !== matchDark; + + return hasMatchMedia === false; +}; + export const WebhookForm = forwardRef< ElementRef, ComponentProps & { @@ -55,8 +100,28 @@ export const WebhookForm = forwardRef< onStateChange?.(state); }); + /** + * Add hidden field generated using js with simple jsdom detector. + * This is used to protect form submission against very simple bots. + */ + const handleSubmitAndAddHiddenJsField = ( + event: React.FormEvent + ) => { + const hiddenInput = document.createElement("input"); + hiddenInput.type = "hidden"; + hiddenInput.name = formBotFieldName; + hiddenInput.value = isJSDom() ? "jsdom" : Date.now().toString(16); + event.currentTarget.appendChild(hiddenInput); + }; + return ( - + {children} From 85e0e590beeae9f6cff321bd43c99d78de0db628 Mon Sep 17 00:00:00 2001 From: istarkov Date: Mon, 13 May 2024 18:35:37 +0300 Subject: [PATCH 2/5] Add fixtures --- .../app/routes/_index.tsx | 116 ++++++++++-------- .../app/routes/[script-test]._index.tsx | 116 ++++++++++-------- .../app/routes/[world]._index.tsx | 116 ++++++++++-------- .../app/routes/_index.tsx | 116 ++++++++++-------- .../app/routes/_index.tsx | 116 ++++++++++-------- .../app/routes/_index.tsx | 116 ++++++++++-------- .../routes/[_route_with_symbols_]._index.tsx | 116 ++++++++++-------- .../app/routes/[form]._index.tsx | 116 ++++++++++-------- .../app/routes/[heading-with-id]._index.tsx | 116 ++++++++++-------- .../routes/[nested].[nested-page]._index.tsx | 116 ++++++++++-------- .../app/routes/[radix]._index.tsx | 116 ++++++++++-------- .../app/routes/[resources]._index.tsx | 116 ++++++++++-------- .../app/routes/_index.tsx | 116 ++++++++++-------- 13 files changed, 858 insertions(+), 650 deletions(-) diff --git a/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx b/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx index 5ed3bcbb163c..b0e312a1e08e 100644 --- a/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx +++ b/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx b/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx index 05ee991c1103..c3b404d6d9e8 100644 --- a/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx b/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx index 0894462f43ad..c891108ff744 100644 --- a/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-custom-template/app/routes/_index.tsx b/fixtures/webstudio-custom-template/app/routes/_index.tsx index 5ed3bcbb163c..b0e312a1e08e 100644 --- a/fixtures/webstudio-custom-template/app/routes/_index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/_index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx b/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx index 5ed3bcbb163c..b0e312a1e08e 100644 --- a/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx b/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx index 5ed3bcbb163c..b0e312a1e08e 100644 --- a/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx index 000e76941c52..224fbfa975e6 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx index e9f71076863e..c005b2e74e7f 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx index 184b0966d318..af7f5d3bce52 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx index ba9909169c81..bbf857a64c5f 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx index ec310daad483..4ff411d678a0 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx index b7289a4c837e..e247b5d28b60 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { diff --git a/fixtures/webstudio-remix-vercel/app/routes/_index.tsx b/fixtures/webstudio-remix-vercel/app/routes/_index.tsx index 5ed3bcbb163c..b0e312a1e08e 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/_index.tsx @@ -11,7 +11,11 @@ import { } from "@remix-run/server-runtime"; import { useLoaderData } from "@remix-run/react"; import { ReactSdkContext } from "@webstudio-is/react-sdk"; -import { n8nHandler, getFormId } from "@webstudio-is/form-handlers"; +import { + n8nHandler, + formIdFieldName, + formBotFieldName, +} from "@webstudio-is/form-handlers"; import { Page, siteName, @@ -241,65 +245,77 @@ const getMethod = (value: string | undefined) => { }; export const action = async ({ request, context }: ActionFunctionArgs) => { - const formData = await request.formData(); + try { + const formData = await request.formData(); - const formId = getFormId(formData); - if (formId === undefined) { - // We're throwing rather than returning { success: false } - // because this isn't supposed to happen normally: bug or malicious user - throw json("Form not found", { status: 404 }); - } + const formId = formData.get(formIdFieldName); - const formProperties = formsProperties.get(formId); + if (formId == null || typeof formId !== "string") { + throw new Error("No form id in FormData"); + } - // form properties are not defined when defaults are used - const { action, method } = formProperties ?? {}; + const formBotValue = formData.get(formBotFieldName); - if (contactEmail === undefined) { - return { success: false }; - } + if (formBotValue == null || typeof formBotValue !== "string") { + throw new Error("Form bot field not found"); + } - // wrapped in try/catch just in cases new URL() throws - // (should not happen) - let pageUrl: URL; - try { - pageUrl = new URL(request.url); + const submitTime = parseInt(formBotValue, 16); + // 5 minutes + if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + throw new Error(`Form bot value invalid ${formBotValue}`); + } + + const formProperties = formsProperties.get(formId); + + // form properties are not defined when defaults are used + const { action, method } = formProperties ?? {}; + + if (contactEmail === undefined) { + throw new Error("Contact email not found"); + } + + const pageUrl = new URL(request.url); pageUrl.host = getRequestHost(request); - } catch { - return { success: false }; - } - if (action !== undefined) { - try { - // Test that action is full URL - new URL(action); - } catch { - return json( - { - success: false, - error: "Invalid action URL, must be valid http/https protocol", - }, - { status: 200 } - ); + if (action !== undefined) { + try { + // Test that action is full URL + new URL(action); + } catch { + throw new Error( + "Invalid action URL, must be valid http/https protocol" + ); + } } - } - const formInfo = { - formData, - projectId, - action: action ?? null, - method: getMethod(method), - pageUrl: pageUrl.toString(), - toEmail: contactEmail, - fromEmail: pageUrl.hostname + "@webstudio.email", - } as const; - - const result = await n8nHandler({ - formInfo, - hookUrl: context.N8N_FORM_EMAIL_HOOK, - }); + const formInfo = { + formData, + projectId, + action: action ?? null, + method: getMethod(method), + pageUrl: pageUrl.toString(), + toEmail: contactEmail, + fromEmail: pageUrl.hostname + "@webstudio.email", + } as const; + + const result = await n8nHandler({ + formInfo, + hookUrl: context.N8N_FORM_EMAIL_HOOK, + }); - return result; + return result; + } catch (error) { + console.error(error); + + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 200 } + ); + } }; const Outlet = () => { From 1a5e44e561cad4ee2462ccdd841d85f6093251bf Mon Sep 17 00:00:00 2001 From: istarkov Date: Mon, 13 May 2024 18:50:15 +0300 Subject: [PATCH 3/5] Fix types --- .../app/routes/_index.tsx | 18 ++++++++++-------- .../app/routes/[script-test]._index.tsx | 18 ++++++++++-------- .../app/routes/[world]._index.tsx | 18 ++++++++++-------- .../app/routes/_index.tsx | 18 ++++++++++-------- .../app/routes/_index.tsx | 18 ++++++++++-------- .../app/routes/_index.tsx | 18 ++++++++++-------- .../routes/[_route_with_symbols_]._index.tsx | 18 ++++++++++-------- .../app/routes/[form]._index.tsx | 18 ++++++++++-------- .../app/routes/[heading-with-id]._index.tsx | 18 ++++++++++-------- .../routes/[nested].[nested-page]._index.tsx | 18 ++++++++++-------- .../app/routes/[radix]._index.tsx | 18 ++++++++++-------- .../app/routes/[resources]._index.tsx | 18 ++++++++++-------- .../app/routes/_index.tsx | 18 ++++++++++-------- .../defaults/app/route-templates/html.tsx | 18 ++++++++++-------- 14 files changed, 140 insertions(+), 112 deletions(-) diff --git a/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx b/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx index b0e312a1e08e..e8340acd13fb 100644 --- a/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx +++ b/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx b/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx index c3b404d6d9e8..9b348ff4a71d 100644 --- a/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx b/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx index c891108ff744..d11a2cf51d87 100644 --- a/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-custom-template/app/routes/_index.tsx b/fixtures/webstudio-custom-template/app/routes/_index.tsx index b0e312a1e08e..e8340acd13fb 100644 --- a/fixtures/webstudio-custom-template/app/routes/_index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/_index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx b/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx index b0e312a1e08e..e8340acd13fb 100644 --- a/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx b/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx index b0e312a1e08e..e8340acd13fb 100644 --- a/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx index 224fbfa975e6..5fa7004c98a2 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx index c005b2e74e7f..f574e837c1ea 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx index af7f5d3bce52..c4f95d45e6b0 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx index bbf857a64c5f..2f35feb0710d 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx index 4ff411d678a0..9f78fc2381ff 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx index e247b5d28b60..7e6c36bc09a5 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/fixtures/webstudio-remix-vercel/app/routes/_index.tsx b/fixtures/webstudio-remix-vercel/app/routes/_index.tsx index b0e312a1e08e..e8340acd13fb 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/_index.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; diff --git a/packages/cli/templates/defaults/app/route-templates/html.tsx b/packages/cli/templates/defaults/app/route-templates/html.tsx index ec441515a323..8fabbe4a4524 100644 --- a/packages/cli/templates/defaults/app/route-templates/html.tsx +++ b/packages/cli/templates/defaults/app/route-templates/html.tsx @@ -244,7 +244,12 @@ const getMethod = (value: string | undefined) => { } }; -export const action = async ({ request, context }: ActionFunctionArgs) => { +export const action = async ({ + request, + context, +}: ActionFunctionArgs): Promise< + { success: true } | { success: false; errors: string[] } +> => { try { const formData = await request.formData(); @@ -308,13 +313,10 @@ export const action = async ({ request, context }: ActionFunctionArgs) => { } catch (error) { console.error(error); - return json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 200 } - ); + return { + success: false, + errors: [error instanceof Error ? error.message : "Unknown error"], + }; } }; From ba8100c3653e677286beb9a685682c9ab4936737 Mon Sep 17 00:00:00 2001 From: istarkov Date: Mon, 13 May 2024 19:02:09 +0300 Subject: [PATCH 4/5] Fix --- fixtures/webstudio-cloudflare-template/app/routes/_index.tsx | 5 ++++- .../app/routes/[script-test]._index.tsx | 5 ++++- .../webstudio-custom-template/app/routes/[world]._index.tsx | 5 ++++- fixtures/webstudio-custom-template/app/routes/_index.tsx | 5 ++++- .../app/routes/_index.tsx | 5 ++++- .../webstudio-remix-netlify-functions/app/routes/_index.tsx | 5 ++++- .../app/routes/[_route_with_symbols_]._index.tsx | 5 ++++- fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx | 5 ++++- .../app/routes/[heading-with-id]._index.tsx | 5 ++++- .../app/routes/[nested].[nested-page]._index.tsx | 5 ++++- .../webstudio-remix-vercel/app/routes/[radix]._index.tsx | 5 ++++- .../webstudio-remix-vercel/app/routes/[resources]._index.tsx | 5 ++++- fixtures/webstudio-remix-vercel/app/routes/_index.tsx | 5 ++++- packages/cli/templates/defaults/app/route-templates/html.tsx | 5 ++++- 14 files changed, 56 insertions(+), 14 deletions(-) diff --git a/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx b/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx index e8340acd13fb..7857f961298c 100644 --- a/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx +++ b/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx b/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx index 9b348ff4a71d..2994e77d7796 100644 --- a/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx b/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx index d11a2cf51d87..309b5877df29 100644 --- a/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-custom-template/app/routes/_index.tsx b/fixtures/webstudio-custom-template/app/routes/_index.tsx index e8340acd13fb..7857f961298c 100644 --- a/fixtures/webstudio-custom-template/app/routes/_index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/_index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx b/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx index e8340acd13fb..7857f961298c 100644 --- a/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx b/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx index e8340acd13fb..7857f961298c 100644 --- a/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx index 5fa7004c98a2..682ca81af5ee 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx index f574e837c1ea..769db973b8f7 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx index c4f95d45e6b0..9bd58053aa13 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx index 2f35feb0710d..bcd78b155c8f 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx index 9f78fc2381ff..7656b7fd9a2f 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx index 7e6c36bc09a5..0f42ed0f0af8 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/fixtures/webstudio-remix-vercel/app/routes/_index.tsx b/fixtures/webstudio-remix-vercel/app/routes/_index.tsx index e8340acd13fb..7857f961298c 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/_index.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } diff --git a/packages/cli/templates/defaults/app/route-templates/html.tsx b/packages/cli/templates/defaults/app/route-templates/html.tsx index 8fabbe4a4524..970ca1d4cd91 100644 --- a/packages/cli/templates/defaults/app/route-templates/html.tsx +++ b/packages/cli/templates/defaults/app/route-templates/html.tsx @@ -267,7 +267,10 @@ export const action = async ({ const submitTime = parseInt(formBotValue, 16); // 5 minutes - if (Math.abs(Date.now() - submitTime) > 1000 * 60 * 5) { + if ( + Number.isNaN(submitTime) || + Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 + ) { throw new Error(`Form bot value invalid ${formBotValue}`); } From 147dff7e7fc5e4fb0535631a5e42f5a4e8ce899b Mon Sep 17 00:00:00 2001 From: istarkov Date: Tue, 14 May 2024 18:54:15 +0300 Subject: [PATCH 5/5] Fix --- fixtures/webstudio-cloudflare-template/app/routes/_index.tsx | 5 ++++- .../app/routes/[script-test]._index.tsx | 5 ++++- .../webstudio-custom-template/app/routes/[world]._index.tsx | 5 ++++- fixtures/webstudio-custom-template/app/routes/_index.tsx | 5 ++++- .../app/routes/_index.tsx | 5 ++++- .../webstudio-remix-netlify-functions/app/routes/_index.tsx | 5 ++++- .../app/routes/[_route_with_symbols_]._index.tsx | 5 ++++- fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx | 5 ++++- .../app/routes/[heading-with-id]._index.tsx | 5 ++++- .../app/routes/[nested].[nested-page]._index.tsx | 5 ++++- .../webstudio-remix-vercel/app/routes/[radix]._index.tsx | 5 ++++- .../webstudio-remix-vercel/app/routes/[resources]._index.tsx | 5 ++++- fixtures/webstudio-remix-vercel/app/routes/_index.tsx | 5 ++++- packages/cli/templates/defaults/app/route-templates/html.tsx | 5 ++++- packages/sdk-components-react-remix/src/webhook-form.tsx | 1 + 15 files changed, 57 insertions(+), 14 deletions(-) diff --git a/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx b/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx index 7857f961298c..e1800c884599 100644 --- a/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx +++ b/fixtures/webstudio-cloudflare-template/app/routes/_index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx b/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx index 2994e77d7796..fab658fbb10d 100644 --- a/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/[script-test]._index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx b/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx index 309b5877df29..8e6a462c0bbc 100644 --- a/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/[world]._index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-custom-template/app/routes/_index.tsx b/fixtures/webstudio-custom-template/app/routes/_index.tsx index 7857f961298c..e1800c884599 100644 --- a/fixtures/webstudio-custom-template/app/routes/_index.tsx +++ b/fixtures/webstudio-custom-template/app/routes/_index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx b/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx index 7857f961298c..e1800c884599 100644 --- a/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-netlify-edge-functions/app/routes/_index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx b/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx index 7857f961298c..e1800c884599 100644 --- a/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-netlify-functions/app/routes/_index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx index 682ca81af5ee..c50fc1587a9e 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[_route_with_symbols_]._index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx index 769db973b8f7..78eda705a17f 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[form]._index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx index 9bd58053aa13..54b3c9b2b01a 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[heading-with-id]._index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx index bcd78b155c8f..79ffef799ce0 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[nested].[nested-page]._index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx index 7656b7fd9a2f..3235310a9d31 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[radix]._index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx b/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx index 0f42ed0f0af8..e69b39912f73 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/[resources]._index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/fixtures/webstudio-remix-vercel/app/routes/_index.tsx b/fixtures/webstudio-remix-vercel/app/routes/_index.tsx index 7857f961298c..e1800c884599 100644 --- a/fixtures/webstudio-remix-vercel/app/routes/_index.tsx +++ b/fixtures/webstudio-remix-vercel/app/routes/_index.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/packages/cli/templates/defaults/app/route-templates/html.tsx b/packages/cli/templates/defaults/app/route-templates/html.tsx index 970ca1d4cd91..c337e6f00d03 100644 --- a/packages/cli/templates/defaults/app/route-templates/html.tsx +++ b/packages/cli/templates/defaults/app/route-templates/html.tsx @@ -266,7 +266,10 @@ export const action = async ({ } const submitTime = parseInt(formBotValue, 16); - // 5 minutes + // Assumes that the difference between the server time and the form submission time, + // including any client-server time drift, is within a 5-minute range. + // Note: submitTime might be NaN because formBotValue can be any string used for logging purposes. + // Example: `formBotValue: jsdom`, or `formBotValue: headless-env` if ( Number.isNaN(submitTime) || Math.abs(Date.now() - submitTime) > 1000 * 60 * 5 diff --git a/packages/sdk-components-react-remix/src/webhook-form.tsx b/packages/sdk-components-react-remix/src/webhook-form.tsx index addb75863d30..0f244bae571b 100644 --- a/packages/sdk-components-react-remix/src/webhook-form.tsx +++ b/packages/sdk-components-react-remix/src/webhook-form.tsx @@ -110,6 +110,7 @@ export const WebhookForm = forwardRef< const hiddenInput = document.createElement("input"); hiddenInput.type = "hidden"; hiddenInput.name = formBotFieldName; + // Non-numeric values are utilized for logging purposes. hiddenInput.value = isJSDom() ? "jsdom" : Date.now().toString(16); event.currentTarget.appendChild(hiddenInput); };