diff --git a/packages/cli/templates/form-trigger-completion.handlebars b/packages/cli/templates/form-trigger-completion.handlebars index a15855d371..d6447128a5 100644 --- a/packages/cli/templates/form-trigger-completion.handlebars +++ b/packages/cli/templates/form-trigger-completion.handlebars @@ -26,49 +26,53 @@ -
-
-
-
-

{{title}}

-

{{message}}

+ {{#if responseText}} + {{responseText}} + {{else}} +
+
+
+
+

{{title}}

+

{{message}}

+
-
- {{#if appendAttribution}} - - {{/if}} -
-
+ {{#if appendAttribution}} + + {{/if}} + + + {{/if}} diff --git a/packages/nodes-base/nodes/Form/Form.node.ts b/packages/nodes-base/nodes/Form/Form.node.ts index 82d8d4c7e4..0e4af9d306 100644 --- a/packages/nodes-base/nodes/Form/Form.node.ts +++ b/packages/nodes-base/nodes/Form/Form.node.ts @@ -115,6 +115,16 @@ const completionProperties = updateDisplayOptions( value: 'redirect', description: 'Redirect the user to a URL', }, + { + name: 'Show Text', + value: 'showText', + description: 'Display simple text or HTML', + }, + { + name: 'Return Binary File', + value: 'returnBinary', + description: 'Return incoming binary file', + }, ], }, { @@ -138,7 +148,7 @@ const completionProperties = updateDisplayOptions( required: true, displayOptions: { show: { - respondWith: ['text'], + respondWith: ['text', 'returnBinary'], }, }, }, @@ -152,10 +162,41 @@ const completionProperties = updateDisplayOptions( }, displayOptions: { show: { - respondWith: ['text'], + respondWith: ['text', 'returnBinary'], }, }, }, + { + displayName: 'Text', + name: 'responseText', + type: 'string', + displayOptions: { + show: { + respondWith: ['showText'], + }, + }, + typeOptions: { + rows: 2, + }, + default: '', + placeholder: 'e.g. Thanks for filling the form', + description: 'The text to display on the page. Use HTML to show a customized web page.', + }, + { + displayName: 'Input Data Field Name', + name: 'inputDataFieldName', + type: 'string', + displayOptions: { + show: { + respondWith: ['returnBinary'], + }, + }, + default: 'data', + placeholder: 'e.g. data', + description: + 'Find the name of input field containing the binary data to return in the Input panel on the left, in the Binary tab', + hint: 'The name of the input field containing the binary file data to be returned', + }, { displayName: 'Options', name: 'options', @@ -165,7 +206,7 @@ const completionProperties = updateDisplayOptions( options: [{ ...formTitle, required: false, displayName: 'Completion Page Title' }], displayOptions: { show: { - respondWith: ['text'], + respondWith: ['text', 'returnBinary'], }, }, }, diff --git a/packages/nodes-base/nodes/Form/formCompletionUtils.ts b/packages/nodes-base/nodes/Form/formCompletionUtils.ts index b1dab0c68c..bd1fd52028 100644 --- a/packages/nodes-base/nodes/Form/formCompletionUtils.ts +++ b/packages/nodes-base/nodes/Form/formCompletionUtils.ts @@ -3,7 +3,49 @@ import { type NodeTypeAndVersion, type IWebhookFunctions, type IWebhookResponseData, + type IN8nHttpResponse, + type IDataObject, + ApplicationError, + type IBinaryData, + BINARY_ENCODING, } from 'n8n-workflow'; +import { type Readable } from 'node:stream'; + +import { sanitizeHtml } from './utils'; + +const getBinaryDataFromNode = (context: IWebhookFunctions, nodeName: string): IDataObject => { + return context.evaluateExpression(`{{ $('${nodeName}').first().binary }}`) as IDataObject; +}; + +const respondWithBinary = (context: IWebhookFunctions, res: Response) => { + const inputDataFieldName = context.getNodeParameter('inputDataFieldName', '') as string; + + const parentNodes = context.getParentNodes(context.getNode().name); + + const binaryNode = parentNodes.find((node) => + getBinaryDataFromNode(context, node?.name)?.hasOwnProperty(inputDataFieldName), + ); + + if (!binaryNode) { + throw new ApplicationError('No binary data found.'); + } + + const binaryData = getBinaryDataFromNode(context, binaryNode?.name)[ + inputDataFieldName + ] as IBinaryData; + + let responseBody: IN8nHttpResponse | Readable; + if (binaryData.id) { + responseBody = { binaryData }; + } else { + responseBody = Buffer.from(binaryData.data, BINARY_ENCODING); + } + + // res.setHeader('Content-Type', binaryData.mimeType); + // res.send(responseBody); + + return { noWebhookResponse: true }; +}; export const renderFormCompletion = async ( context: IWebhookFunctions, @@ -14,6 +56,12 @@ export const renderFormCompletion = async ( const completionMessage = context.getNodeParameter('completionMessage', '') as string; const redirectUrl = context.getNodeParameter('redirectUrl', '') as string; const options = context.getNodeParameter('options', {}) as { formTitle: string }; + const respondWith = context.getNodeParameter('respondWith', '') as string; + const responseText = context.getNodeParameter('responseText', '') as string; + + if (respondWith === 'returnBinary') { + return respondWithBinary(context, res); + } if (redirectUrl) { res.send( @@ -35,6 +83,7 @@ export const renderFormCompletion = async ( message: completionMessage, formTitle: title, appendAttribution, + responseText: sanitizeHtml(responseText), }); return { noWebhookResponse: true }; diff --git a/packages/nodes-base/nodes/Webhook/Webhook.node.ts b/packages/nodes-base/nodes/Webhook/Webhook.node.ts index 9255dab346..c540c0bfdc 100644 --- a/packages/nodes-base/nodes/Webhook/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook/Webhook.node.ts @@ -220,7 +220,7 @@ export class Webhook extends Node { }); if (options.binaryData) { - return await this.handleBinaryData(context, prepareOutput); + return await this.handleBinaryData(context, prepareOutput); // magic here } if (req.contentType === 'multipart/form-data') {