From 86a20f656389474cb9fb26acf406de4e7af7b34c Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:16:57 +0300 Subject: [PATCH] fix(Respond to Webhook Node): Continue on fail and error branch support (#9115) --- .../RespondToWebhook/RespondToWebhook.node.ts | 256 ++++++++++-------- 1 file changed, 136 insertions(+), 120 deletions(-) diff --git a/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts b/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts index fb1c2db13f..a2b2988fc9 100644 --- a/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts +++ b/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts @@ -11,7 +11,7 @@ import type { import { jsonParse, BINARY_ENCODING, NodeOperationError } from 'n8n-workflow'; import set from 'lodash/set'; import jwt from 'jsonwebtoken'; -import { formatPrivateKey } from '../../utils/utilities'; +import { formatPrivateKey, generatePairedItemData } from '../../utils/utilities'; export class RespondToWebhook implements INodeType { description: INodeTypeDescription = { @@ -287,142 +287,158 @@ export class RespondToWebhook implements INodeType { }; async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); const nodeVersion = this.getNode().typeVersion; - if (nodeVersion >= 1.1) { - const connectedNodes = this.getParentNodes(this.getNode().name); - if (!connectedNodes.some((node) => node.type === 'n8n-nodes-base.webhook')) { - throw new NodeOperationError( - this.getNode(), - new Error('No Webhook node found in the workflow'), - { - description: - 'Insert a Webhook node to your workflow and set the “Respond” parameter to “Using Respond to Webhook Node” ', - }, - ); - } - } - const items = this.getInputData(); - - const respondWith = this.getNodeParameter('respondWith', 0) as string; - const options = this.getNodeParameter('options', 0, {}); - - const headers = {} as IDataObject; - if (options.responseHeaders) { - for (const header of (options.responseHeaders as IDataObject).entries as IDataObject[]) { - if (typeof header.name !== 'string') { - header.name = header.name?.toString(); - } - headers[header.name?.toLowerCase() as string] = header.value?.toString(); - } - } - - let statusCode = (options.responseCode as number) || 200; - let responseBody: IN8nHttpResponse | Readable; - if (respondWith === 'json') { - const responseBodyParameter = this.getNodeParameter('responseBody', 0) as string; - if (responseBodyParameter) { - if (typeof responseBodyParameter === 'object') { - responseBody = responseBodyParameter; - } else { - try { - responseBody = jsonParse(responseBodyParameter); - } catch (error) { - throw new NodeOperationError(this.getNode(), error as Error, { - message: "Invalid JSON in 'Response Body' field", + try { + if (nodeVersion >= 1.1) { + const connectedNodes = this.getParentNodes(this.getNode().name); + if (!connectedNodes.some((node) => node.type === 'n8n-nodes-base.webhook')) { + throw new NodeOperationError( + this.getNode(), + new Error('No Webhook node found in the workflow'), + { description: - "Check that the syntax of the JSON in the 'Response Body' parameter is valid", - }); + 'Insert a Webhook node to your workflow and set the “Respond” parameter to “Using Respond to Webhook Node” ', + }, + ); + } + } + + const respondWith = this.getNodeParameter('respondWith', 0) as string; + const options = this.getNodeParameter('options', 0, {}); + + const headers = {} as IDataObject; + if (options.responseHeaders) { + for (const header of (options.responseHeaders as IDataObject).entries as IDataObject[]) { + if (typeof header.name !== 'string') { + header.name = header.name?.toString(); + } + headers[header.name?.toLowerCase() as string] = header.value?.toString(); + } + } + + let statusCode = (options.responseCode as number) || 200; + let responseBody: IN8nHttpResponse | Readable; + if (respondWith === 'json') { + const responseBodyParameter = this.getNodeParameter('responseBody', 0) as string; + if (responseBodyParameter) { + if (typeof responseBodyParameter === 'object') { + responseBody = responseBodyParameter; + } else { + try { + responseBody = jsonParse(responseBodyParameter); + } catch (error) { + throw new NodeOperationError(this.getNode(), error as Error, { + message: "Invalid JSON in 'Response Body' field", + description: + "Check that the syntax of the JSON in the 'Response Body' parameter is valid", + }); + } } } - } - } else if (respondWith === 'jwt') { - try { - const { keyType, secret, algorithm, privateKey } = (await this.getCredentials( - 'jwtAuth', - )) as { - keyType: 'passphrase' | 'pemKey'; - privateKey: string; - secret: string; - algorithm: jwt.Algorithm; - }; + } else if (respondWith === 'jwt') { + try { + const { keyType, secret, algorithm, privateKey } = (await this.getCredentials( + 'jwtAuth', + )) as { + keyType: 'passphrase' | 'pemKey'; + privateKey: string; + secret: string; + algorithm: jwt.Algorithm; + }; - let secretOrPrivateKey; + let secretOrPrivateKey; - if (keyType === 'passphrase') { - secretOrPrivateKey = secret; - } else { - secretOrPrivateKey = formatPrivateKey(privateKey); + if (keyType === 'passphrase') { + secretOrPrivateKey = secret; + } else { + secretOrPrivateKey = formatPrivateKey(privateKey); + } + const payload = this.getNodeParameter('payload', 0, {}) as IDataObject; + const token = jwt.sign(payload, secretOrPrivateKey, { algorithm }); + responseBody = { token }; + } catch (error) { + throw new NodeOperationError(this.getNode(), error as Error, { + message: 'Error signing JWT token', + }); } - const payload = this.getNodeParameter('payload', 0, {}) as IDataObject; - const token = jwt.sign(payload, secretOrPrivateKey, { algorithm }); - responseBody = { token }; - } catch (error) { - throw new NodeOperationError(this.getNode(), error as Error, { - message: 'Error signing JWT token', - }); - } - } else if (respondWith === 'allIncomingItems') { - const respondItems = items.map((item) => item.json); - responseBody = options.responseKey - ? set({}, options.responseKey as string, respondItems) - : respondItems; - } else if (respondWith === 'firstIncomingItem') { - responseBody = options.responseKey - ? set({}, options.responseKey as string, items[0].json) - : items[0].json; - } else if (respondWith === 'text') { - responseBody = this.getNodeParameter('responseBody', 0) as string; - } else if (respondWith === 'binary') { - const item = items[0]; + } else if (respondWith === 'allIncomingItems') { + const respondItems = items.map((item) => item.json); + responseBody = options.responseKey + ? set({}, options.responseKey as string, respondItems) + : respondItems; + } else if (respondWith === 'firstIncomingItem') { + responseBody = options.responseKey + ? set({}, options.responseKey as string, items[0].json) + : items[0].json; + } else if (respondWith === 'text') { + responseBody = this.getNodeParameter('responseBody', 0) as string; + } else if (respondWith === 'binary') { + const item = items[0]; - if (item.binary === undefined) { - throw new NodeOperationError(this.getNode(), 'No binary data exists on the first item!'); - } - - let responseBinaryPropertyName: string; - - const responseDataSource = this.getNodeParameter('responseDataSource', 0) as string; - - if (responseDataSource === 'set') { - responseBinaryPropertyName = this.getNodeParameter('inputFieldName', 0) as string; - } else { - const binaryKeys = Object.keys(item.binary); - if (binaryKeys.length === 0) { + if (item.binary === undefined) { throw new NodeOperationError(this.getNode(), 'No binary data exists on the first item!'); } - responseBinaryPropertyName = binaryKeys[0]; + + let responseBinaryPropertyName: string; + + const responseDataSource = this.getNodeParameter('responseDataSource', 0) as string; + + if (responseDataSource === 'set') { + responseBinaryPropertyName = this.getNodeParameter('inputFieldName', 0) as string; + } else { + const binaryKeys = Object.keys(item.binary); + if (binaryKeys.length === 0) { + throw new NodeOperationError( + this.getNode(), + 'No binary data exists on the first item!', + ); + } + responseBinaryPropertyName = binaryKeys[0]; + } + + const binaryData = this.helpers.assertBinaryData(0, responseBinaryPropertyName); + if (binaryData.id) { + responseBody = { binaryData }; + } else { + responseBody = Buffer.from(binaryData.data, BINARY_ENCODING); + headers['content-length'] = (responseBody as Buffer).length; + } + + if (!headers['content-type']) { + headers['content-type'] = binaryData.mimeType; + } + } else if (respondWith === 'redirect') { + headers.location = this.getNodeParameter('redirectURL', 0) as string; + statusCode = (options.responseCode as number) ?? 307; + } else if (respondWith !== 'noData') { + throw new NodeOperationError( + this.getNode(), + `The Response Data option "${respondWith}" is not supported!`, + ); } - const binaryData = this.helpers.assertBinaryData(0, responseBinaryPropertyName); - if (binaryData.id) { - responseBody = { binaryData }; - } else { - responseBody = Buffer.from(binaryData.data, BINARY_ENCODING); - headers['content-length'] = (responseBody as Buffer).length; + const response: IN8nHttpFullResponse = { + body: responseBody, + headers, + statusCode, + }; + + this.sendResponse(response); + } catch (error) { + if (this.continueOnFail()) { + const itemData = generatePairedItemData(items.length); + const returnData = this.helpers.constructExecutionMetaData( + [{ json: { error: error.message } }], + { itemData }, + ); + return [returnData]; } - if (!headers['content-type']) { - headers['content-type'] = binaryData.mimeType; - } - } else if (respondWith === 'redirect') { - headers.location = this.getNodeParameter('redirectURL', 0) as string; - statusCode = (options.responseCode as number) ?? 307; - } else if (respondWith !== 'noData') { - throw new NodeOperationError( - this.getNode(), - `The Response Data option "${respondWith}" is not supported!`, - ); + throw error; } - const response: IN8nHttpFullResponse = { - body: responseBody, - headers, - statusCode, - }; - - this.sendResponse(response); - return [items]; } }