From 4e28fb00b744b41f3efa6548b4f78add1a9e85a7 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Mon, 8 Sep 2025 01:46:05 +0300 Subject: [PATCH 1/3] fix: handle headers in graphql resource --- .../features/settings-panel/resource-panel.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/builder/app/builder/features/settings-panel/resource-panel.tsx b/apps/builder/app/builder/features/settings-panel/resource-panel.tsx index a902ea12b76d..71dfdf34533d 100644 --- a/apps/builder/app/builder/features/settings-panel/resource-panel.tsx +++ b/apps/builder/app/builder/features/settings-panel/resource-panel.tsx @@ -796,7 +796,10 @@ const parseHeaders = (headers: Resource["headers"]) => { let maxAge: undefined | string; let bodyType: BodyType; const newHeaders = headers.filter((header) => { - const value = computeExpression(header.value, new Map()).toLowerCase(); + // cast raw expression result to string + const value = String( + computeExpression(header.value, new Map()) + ).toLowerCase(); if (isCacheControl(header.name)) { // move simple header like Cache-Control: max-age=10 to dedicated input // preserve more complex cache-control @@ -818,7 +821,7 @@ const parseHeaders = (headers: Resource["headers"]) => { return false; } } - return false; + return true; }); return { headers: newHeaders, maxAge, bodyType }; }; @@ -1129,10 +1132,9 @@ export const GraphqlResourceForm = forwardRef< control: "graphql", url, method: "post", - headers: [ - ...headers, - { name: "Content-Type", value: "application/json" }, - ], + headers: headers.some(({ name }) => isContentType(name)) + ? headers + : [...headers, { name: "Content-Type", value: `"application/json"` }], body, }; const newVariable: DataSource = { From c49b838afb865911111d20f23caa98f985db9c3f Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Mon, 8 Sep 2025 10:43:34 +0300 Subject: [PATCH 2/3] Fix resource preview --- .../features/settings-panel/resource-panel.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/builder/app/builder/features/settings-panel/resource-panel.tsx b/apps/builder/app/builder/features/settings-panel/resource-panel.tsx index 71dfdf34533d..63950558e657 100644 --- a/apps/builder/app/builder/features/settings-panel/resource-panel.tsx +++ b/apps/builder/app/builder/features/settings-panel/resource-panel.tsx @@ -66,10 +66,7 @@ import { type InstancePath, } from "~/shared/awareness"; import { updateWebstudioData } from "~/shared/instance-utils"; -import { - computeExpression, - rebindTreeVariablesMutable, -} from "~/shared/data-variables"; +import { rebindTreeVariablesMutable } from "~/shared/data-variables"; import { parseCurl, type CurlRequest } from "./curl"; export const parseResource = ({ @@ -558,7 +555,7 @@ export const getResourceScopeForInstance = ({ const name = encodeDataVariableId(dataSourceId); scope[name] = value; aliases.set(name, dataSource.name); - variableValues.set(dataSource.name, value); + variableValues.set(dataSourceId, value); } } } @@ -621,7 +618,7 @@ export const useResourceScope = ({ variable }: { variable?: DataSource }) => { const key = encodeDataVariableId(variable.id); delete newScope[key]; newAliases.delete(key); - newVariableValues.delete(variable.name); + newVariableValues.delete(variable.id); } return { scope: newScope, @@ -798,7 +795,7 @@ const parseHeaders = (headers: Resource["headers"]) => { const newHeaders = headers.filter((header) => { // cast raw expression result to string const value = String( - computeExpression(header.value, new Map()) + evaluateExpressionWithinScope(header.value, {}) ).toLowerCase(); if (isCacheControl(header.name)) { // move simple header like Cache-Control: max-age=10 to dedicated input @@ -1180,6 +1177,7 @@ export const GraphqlResourceForm = forwardRef< } }} /> + From 44228cb41bfaa55ccc0bb323a275f7a61eaaccd5 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Mon, 8 Sep 2025 17:25:36 +0300 Subject: [PATCH 3/3] Fix reload and sitemap resource --- .../settings-panel/resource-panel.tsx | 75 ++++++++++--------- packages/sdk/src/resource-loader.ts | 22 ++++-- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/apps/builder/app/builder/features/settings-panel/resource-panel.tsx b/apps/builder/app/builder/features/settings-panel/resource-panel.tsx index 63950558e657..fa70518756d3 100644 --- a/apps/builder/app/builder/features/settings-panel/resource-panel.tsx +++ b/apps/builder/app/builder/features/settings-panel/resource-panel.tsx @@ -71,10 +71,12 @@ import { parseCurl, type CurlRequest } from "./curl"; export const parseResource = ({ id, + control, name, formData, }: { id: string; + control?: string; name?: string; formData: FormData; }) => { @@ -84,6 +86,7 @@ export const parseResource = ({ const headerValues = formData.getAll("header-value") as string[]; return Resource.parse({ id, + control, name: name ?? formData.get("name"), url: formData.get("url"), searchParams: searchParamNames @@ -250,7 +253,7 @@ const SearchParamPair = ({ }) => { const evaluatedValue = evaluateExpressionWithinScope(value, scope); // expressions with variables or objects cannot be edited from input - const isValueUnbound = + const isValueUnboundString = isLiteralExpression(value) && typeof evaluatedValue === "string"; return ( @@ -281,7 +284,7 @@ const SearchParamPair = ({ onChange(name, newValue)} onRemove={(evaluatedValue) => @@ -374,7 +377,7 @@ const HeaderPair = ({ }) => { const evaluatedValue = evaluateExpressionWithinScope(value, scope); // expressions with variables or objects cannot be edited from input - const isValueUnbound = + const isValueUnboundString = isLiteralExpression(value) && typeof evaluatedValue === "string"; return ( @@ -405,7 +408,7 @@ const HeaderPair = ({ onChange(name, newValue)} onRemove={(evaluatedValue) => @@ -980,8 +983,6 @@ export const SystemResourceForm = forwardRef< ? resources.get(variable.resourceId) : undefined; - const method = "get"; - const localResources = [ { label: "Sitemap", @@ -1006,19 +1007,15 @@ export const SystemResourceForm = forwardRef< if (scopeInstanceId === undefined) { return; } - const name = z.string().parse(formData.get("name")); - const newResource: Resource = { + const newResource: Resource = parseResource({ id: resource?.id ?? nanoid(), - name, control: "system", - url: localResource.value, - method, - headers: [], - }; + formData, + }); const newVariable: DataSource = { id: variable?.id ?? nanoid(), scopeInstanceId, - name, + name: newResource.name, type: "resource", resourceId: newResource.id, }; @@ -1037,6 +1034,8 @@ export const SystemResourceForm = forwardRef< return ( <> + + + {!headers.some(({ name }) => isContentType(name)) && ( + <> + + + + )} + + - diff --git a/packages/sdk/src/resource-loader.ts b/packages/sdk/src/resource-loader.ts index 26bef82d4cbf..b49d0606eba6 100644 --- a/packages/sdk/src/resource-loader.ts +++ b/packages/sdk/src/resource-loader.ts @@ -25,13 +25,19 @@ export const loadResource = async ( ) => { try { const { method, searchParams, headers, body } = resourceRequest; - // cloudflare workers fail when fetching url contains spaces - // even though new URL suppose to trim them on parsing by spec - const url = new URL(resourceRequest.url.trim()); - if (searchParams) { - for (const { name, value } of searchParams) { - url.searchParams.append(name, serializeValue(value)); + let href = resourceRequest.url; + try { + // cloudflare workers fail when fetching url contains spaces + // even though new URL suppose to trim them on parsing by spec + const url = new URL(resourceRequest.url.trim()); + if (searchParams) { + for (const { name, value } of searchParams) { + url.searchParams.append(name, serializeValue(value)); + } } + href = url.href; + } catch { + // empty block } const requestHeaders = new Headers( headers.map(({ name, value }): [string, string] => [ @@ -46,7 +52,7 @@ export const loadResource = async ( if (method !== "get" && body !== undefined) { requestInit.body = serializeValue(body); } - const response = await customFetch(url.href, requestInit); + const response = await customFetch(href, requestInit); let data = await response.text(); @@ -59,7 +65,7 @@ export const loadResource = async ( if (!response.ok) { console.error( - `Failed to load resource: ${url} - ${response.status}: ${JSON.stringify(data).slice(0, 300)}` + `Failed to load resource: ${href} - ${response.status}: ${JSON.stringify(data).slice(0, 300)}` ); }