From f58e3a1e4c30d583754c771caa29a5d1a235519a Mon Sep 17 00:00:00 2001 From: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:59:05 +0300 Subject: [PATCH] refactor(core): Extract functions from executeWebhook (#17152) --- packages/cli/src/webhooks/webhook-helpers.ts | 173 ++++++++++++------- 1 file changed, 106 insertions(+), 67 deletions(-) diff --git a/packages/cli/src/webhooks/webhook-helpers.ts b/packages/cli/src/webhooks/webhook-helpers.ts index 5312cf2aee..c7af885dbf 100644 --- a/packages/cli/src/webhooks/webhook-helpers.ts +++ b/packages/cli/src/webhooks/webhook-helpers.ts @@ -371,38 +371,14 @@ export async function executeWebhook( additionalData.executionId = executionId; } - //check if response mode should be set automatically, e.g. multipage form - const responseMode = - autoDetectResponseMode(workflowStartNode, workflow, req.method) ?? - (workflow.expression.getSimpleParameterValue( - workflowStartNode, - webhookData.webhookDescription.responseMode, - executionMode, - additionalKeys, - undefined, - 'onReceived', - ) as WebhookResponseMode); - - const responseCode = workflow.expression.getSimpleParameterValue( + const { responseMode, responseCode, responseData } = evaluateResponseOptions( workflowStartNode, - webhookData.webhookDescription.responseCode as string, + workflow, + req, + webhookData, executionMode, additionalKeys, - undefined, - 200, - ) as number; - - // This parameter is used for two different purposes: - // 1. as arbitrary string input defined in the workflow in the "respond immediately" mode, - // 2. as well as WebhookResponseData config in all the other modes - const responseData = workflow.expression.getComplexParameterValue( - workflowStartNode, - webhookData.webhookDescription.responseData, - executionMode, - additionalKeys, - undefined, - 'firstEntryJson', - ) as WebhookResponseData | string | undefined; + ); if (!['onReceived', 'lastNode', 'responseNode', 'formPage', 'streaming'].includes(responseMode)) { // If the mode is not known we error. Is probably best like that instead of using @@ -417,21 +393,6 @@ export async function executeWebhook( additionalData.httpRequest = req; additionalData.httpResponse = res; - let binaryData; - - const nodeVersion = workflowStartNode.typeVersion; - if (nodeVersion === 1) { - // binaryData option is removed in versions higher than 1 - binaryData = workflow.expression.getSimpleParameterValue( - workflowStartNode, - '={{$parameter["options"]["binaryData"]}}', - executionMode, - additionalKeys, - undefined, - false, - ); - } - let didSendResponse = false; let runExecutionDataMerge = {}; try { @@ -439,28 +400,7 @@ export async function executeWebhook( // the workflow should be executed or not let webhookResultData: IWebhookResponseData; - // if `Webhook` or `Wait` node, and binaryData is enabled, skip pre-parse the request-body - // always falsy for versions higher than 1 - if (!binaryData) { - const { contentType } = req; - if (contentType === 'multipart/form-data') { - req.body = await parseFormData(req); - } else { - if (nodeVersion > 1) { - if ( - contentType?.startsWith('application/json') || - contentType?.startsWith('text/plain') || - contentType?.startsWith('application/x-www-form-urlencoded') || - contentType?.endsWith('/xml') || - contentType?.endsWith('+xml') - ) { - await parseBody(req); - } - } else { - await parseBody(req); - } - } - } + await parseRequestBody(req, workflowStartNode, workflow, executionMode, additionalKeys); // TODO: remove this hack, and make sure that execution data is properly created before the MCP trigger is executed if (workflowStartNode.type === MCP_TRIGGER_NODE_TYPE) { @@ -565,7 +505,7 @@ export async function executeWebhook( if (!res.headersSent) { // Only set given headers if they haven't been sent yet, e.g. for streaming - if (responseHeaders !== undefined && responseHeaders.entries !== undefined) { + if (responseHeaders?.entries !== undefined) { for (const item of responseHeaders.entries) { res.setHeader(item.name, item.value); } @@ -924,3 +864,102 @@ export async function executeWebhook( return; } } + +/** + * Evaluates the response mode, code and data for a webhook node + */ +function evaluateResponseOptions( + workflowStartNode: INode, + workflow: Workflow, + req: WebhookRequest, + webhookData: IWebhookData, + executionMode: WorkflowExecuteMode, + additionalKeys: IWorkflowDataProxyAdditionalKeys, +) { + //check if response mode should be set automatically, e.g. multipage form + const responseMode = + autoDetectResponseMode(workflowStartNode, workflow, req.method) ?? + (workflow.expression.getSimpleParameterValue( + workflowStartNode, + webhookData.webhookDescription.responseMode, + executionMode, + additionalKeys, + undefined, + 'onReceived', + ) as WebhookResponseMode); + + const responseCode = workflow.expression.getSimpleParameterValue( + workflowStartNode, + webhookData.webhookDescription.responseCode as string, + executionMode, + additionalKeys, + undefined, + 200, + ) as number; + + // This parameter is used for two different purposes: + // 1. as arbitrary string input defined in the workflow in the "respond immediately" mode, + // 2. as well as WebhookResponseData config in all the other modes + const responseData = workflow.expression.getComplexParameterValue( + workflowStartNode, + webhookData.webhookDescription.responseData, + executionMode, + additionalKeys, + undefined, + 'firstEntryJson', + ) as WebhookResponseData | string | undefined; + + return { responseMode, responseCode, responseData }; +} + +/** + * Parses the request body (form, xml, json, form-urlencoded, etc.) if needed + * into the `req.body` property. + */ +async function parseRequestBody( + req: WebhookRequest, + workflowStartNode: INode, + workflow: Workflow, + executionMode: WorkflowExecuteMode, + additionalKeys: IWorkflowDataProxyAdditionalKeys, +) { + let binaryData: string | number | boolean | unknown[] | undefined; + + const nodeVersion = workflowStartNode.typeVersion; + if (nodeVersion === 1) { + // binaryData option is removed in versions higher than 1 + binaryData = workflow.expression.getSimpleParameterValue( + workflowStartNode, + '={{$parameter["options"]["binaryData"]}}', + executionMode, + additionalKeys, + undefined, + false, + ); + } + + // if `Webhook` or `Wait` node, and binaryData is enabled, skip pre-parse the request-body + // always falsy for versions higher than 1 + if (binaryData) { + return; + } + + const { contentType } = req; + if (contentType === 'multipart/form-data') { + req.body = await parseFormData(req); + } else { + if (nodeVersion > 1) { + if ( + contentType?.startsWith('application/json') || + contentType?.startsWith('text/plain') || + contentType?.startsWith('application/x-www-form-urlencoded') || + contentType?.endsWith('/xml') || + contentType?.endsWith('+xml') + ) { + await parseBody(req); + } + } else { + await parseBody(req); + } + } +}