2023-05-17 01:06:24 -07:00
import type { Readable } from 'stream' ;
2023-01-27 03:22:44 -08:00
import type {
2021-11-05 09:45:51 -07:00
IDataObject ,
2023-03-06 08:33:32 -08:00
IExecuteFunctions ,
2021-11-05 09:45:51 -07:00
IN8nHttpFullResponse ,
IN8nHttpResponse ,
INodeExecutionData ,
INodeType ,
INodeTypeDescription ,
} from 'n8n-workflow' ;
2024-09-27 05:23:31 -07:00
import {
jsonParse ,
BINARY_ENCODING ,
NodeOperationError ,
NodeConnectionType ,
WEBHOOK_NODE_TYPE ,
FORM_TRIGGER_NODE_TYPE ,
CHAT_TRIGGER_NODE_TYPE ,
WAIT_NODE_TYPE ,
} from 'n8n-workflow' ;
2023-12-21 04:03:26 -08:00
import set from 'lodash/set' ;
2024-03-28 01:46:39 -07:00
import jwt from 'jsonwebtoken' ;
2024-04-11 03:16:57 -07:00
import { formatPrivateKey , generatePairedItemData } from '../../utils/utilities' ;
2021-11-05 09:45:51 -07:00
export class RespondToWebhook implements INodeType {
description : INodeTypeDescription = {
displayName : 'Respond to Webhook' ,
2024-06-06 04:34:30 -07:00
icon : { light : 'file:webhook.svg' , dark : 'file:webhook.dark.svg' } ,
2021-11-05 09:45:51 -07:00
name : 'respondToWebhook' ,
group : [ 'transform' ] ,
2024-03-28 01:46:39 -07:00
version : [ 1 , 1.1 ] ,
2021-11-05 09:45:51 -07:00
description : 'Returns data for Webhook' ,
defaults : {
name : 'Respond to Webhook' ,
} ,
2024-08-29 06:55:53 -07:00
inputs : [ NodeConnectionType . Main ] ,
outputs : [ NodeConnectionType . Main ] ,
2024-03-28 01:46:39 -07:00
credentials : [
{
name : 'jwtAuth' ,
required : true ,
displayOptions : {
show : {
respondWith : [ 'jwt' ] ,
} ,
} ,
} ,
] ,
2021-11-05 09:45:51 -07:00
properties : [
2023-12-21 04:03:26 -08:00
{
displayName :
'Verify that the "Webhook" node\'s "Respond" parameter is set to "Using Respond to Webhook Node". <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.respondtowebhook/" target="_blank">More details' ,
name : 'generalNotice' ,
type : 'notice' ,
default : '' ,
} ,
2021-11-05 09:45:51 -07:00
{
displayName : 'Respond With' ,
name : 'respondWith' ,
type : 'options' ,
options : [
{
2023-12-21 04:03:26 -08:00
name : 'All Incoming Items' ,
value : 'allIncomingItems' ,
description : 'Respond with all input JSON items' ,
} ,
{
name : 'Binary File' ,
2022-06-03 10:23:49 -07:00
value : 'binary' ,
2023-12-21 04:03:26 -08:00
description : 'Respond with incoming file binary data' ,
2021-11-05 09:45:51 -07:00
} ,
{
2022-06-03 10:23:49 -07:00
name : 'First Incoming Item' ,
value : 'firstIncomingItem' ,
2023-12-21 04:03:26 -08:00
description : 'Respond with the first input JSON item' ,
2021-11-05 09:45:51 -07:00
} ,
{
name : 'JSON' ,
value : 'json' ,
2023-12-21 04:03:26 -08:00
description : 'Respond with a custom JSON body' ,
2021-11-05 09:45:51 -07:00
} ,
2024-03-28 01:46:39 -07:00
{
name : 'JWT Token' ,
value : 'jwt' ,
description : 'Respond with a JWT token' ,
} ,
2021-11-05 09:45:51 -07:00
{
name : 'No Data' ,
value : 'noData' ,
2023-12-21 04:03:26 -08:00
description : 'Respond with an empty body' ,
2021-11-05 09:45:51 -07:00
} ,
2023-12-13 07:00:51 -08:00
{
name : 'Redirect' ,
value : 'redirect' ,
2023-12-21 04:03:26 -08:00
description : 'Respond with a redirect to a given URL' ,
2023-12-13 07:00:51 -08:00
} ,
2022-06-03 10:23:49 -07:00
{
name : 'Text' ,
value : 'text' ,
2023-12-21 04:03:26 -08:00
description : 'Respond with a simple text message body' ,
2022-06-03 10:23:49 -07:00
} ,
2021-11-05 09:45:51 -07:00
] ,
default : 'firstIncomingItem' ,
description : 'The data that should be returned' ,
} ,
2024-03-28 01:46:39 -07:00
{
displayName : 'Credentials' ,
name : 'credentials' ,
type : 'credentials' ,
default : '' ,
displayOptions : {
show : {
respondWith : [ 'jwt' ] ,
} ,
} ,
} ,
2021-11-05 09:45:51 -07:00
{
2022-08-17 08:50:24 -07:00
displayName :
2023-12-21 04:03:26 -08:00
'When using expressions, note that this node will only run for the first item in the input data' ,
2021-11-05 09:45:51 -07:00
name : 'webhookNotice' ,
type : 'notice' ,
displayOptions : {
show : {
2024-03-28 01:46:39 -07:00
respondWith : [ 'json' , 'text' , 'jwt' ] ,
2021-11-05 09:45:51 -07:00
} ,
} ,
default : '' ,
} ,
2023-12-13 07:00:51 -08:00
{
displayName : 'Redirect URL' ,
name : 'redirectURL' ,
type : 'string' ,
required : true ,
displayOptions : {
show : {
respondWith : [ 'redirect' ] ,
} ,
} ,
default : '' ,
placeholder : 'e.g. http://www.n8n.io' ,
description : 'The URL to redirect to' ,
validateType : 'url' ,
} ,
2021-11-05 09:45:51 -07:00
{
displayName : 'Response Body' ,
name : 'responseBody' ,
type : 'json' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
respondWith : [ 'json' ] ,
2021-11-05 09:45:51 -07:00
} ,
} ,
2023-12-21 04:03:26 -08:00
default : '{\n "myField": "value"\n}' ,
typeOptions : {
rows : 4 ,
} ,
description : 'The HTTP response JSON data' ,
2021-11-05 09:45:51 -07:00
} ,
2024-03-28 01:46:39 -07:00
{
displayName : 'Payload' ,
name : 'payload' ,
type : 'json' ,
displayOptions : {
show : {
respondWith : [ 'jwt' ] ,
} ,
} ,
default : '{\n "myField": "value"\n}' ,
typeOptions : {
rows : 4 ,
} ,
validateType : 'object' ,
description : 'The payload to include in the JWT token' ,
} ,
2021-11-05 09:45:51 -07:00
{
displayName : 'Response Body' ,
name : 'responseBody' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
respondWith : [ 'text' ] ,
2021-11-05 09:45:51 -07:00
} ,
} ,
2023-12-21 04:03:26 -08:00
typeOptions : {
rows : 2 ,
} ,
2021-11-05 09:45:51 -07:00
default : '' ,
2023-12-21 04:03:26 -08:00
placeholder : 'e.g. Workflow completed' ,
description : 'The HTTP response text data' ,
2021-11-05 09:45:51 -07:00
} ,
{
displayName : 'Response Data Source' ,
name : 'responseDataSource' ,
type : 'options' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
respondWith : [ 'binary' ] ,
2021-11-05 09:45:51 -07:00
} ,
} ,
options : [
{
name : 'Choose Automatically From Input' ,
value : 'automatically' ,
description : 'Use if input data will contain a single piece of binary data' ,
} ,
{
name : 'Specify Myself' ,
value : 'set' ,
description : 'Enter the name of the input field the binary data will be in' ,
} ,
] ,
default : 'automatically' ,
} ,
{
displayName : 'Input Field Name' ,
name : 'inputFieldName' ,
type : 'string' ,
required : true ,
default : 'data' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
respondWith : [ 'binary' ] ,
responseDataSource : [ 'set' ] ,
2021-11-05 09:45:51 -07:00
} ,
} ,
description : 'The name of the node input field with the binary data' ,
} ,
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
2024-07-29 05:27:23 -07:00
placeholder : 'Add option' ,
2021-11-05 09:45:51 -07:00
default : { } ,
options : [
{
displayName : 'Response Code' ,
name : 'responseCode' ,
type : 'number' ,
typeOptions : {
minValue : 100 ,
maxValue : 599 ,
} ,
default : 200 ,
2023-12-21 04:03:26 -08:00
description : 'The HTTP response code to return. Defaults to 200.' ,
2021-11-05 09:45:51 -07:00
} ,
{
displayName : 'Response Headers' ,
name : 'responseHeaders' ,
placeholder : 'Add Response Header' ,
description : 'Add headers to the webhook response' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : true ,
} ,
default : { } ,
options : [
{
name : 'entries' ,
displayName : 'Entries' ,
values : [
{
displayName : 'Name' ,
name : 'name' ,
type : 'string' ,
default : '' ,
description : 'Name of the header' ,
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
default : '' ,
description : 'Value of the header' ,
} ,
] ,
} ,
] ,
} ,
2023-12-21 04:03:26 -08:00
{
displayName : 'Put Response in Field' ,
name : 'responseKey' ,
type : 'string' ,
displayOptions : {
show : {
[ '/respondWith' ] : [ 'allIncomingItems' , 'firstIncomingItem' ] ,
} ,
} ,
default : '' ,
description : 'The name of the response field to put all items in' ,
placeholder : 'e.g. data' ,
} ,
2021-11-05 09:45:51 -07:00
] ,
} ,
] ,
} ;
2022-01-03 13:42:42 -08:00
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
2024-04-11 03:16:57 -07:00
const items = this . getInputData ( ) ;
2024-03-28 01:46:39 -07:00
const nodeVersion = this . getNode ( ) . typeVersion ;
2024-08-16 00:57:21 -07:00
const WEBHOOK_NODE_TYPES = [
2024-09-27 05:23:31 -07:00
WEBHOOK_NODE_TYPE ,
FORM_TRIGGER_NODE_TYPE ,
CHAT_TRIGGER_NODE_TYPE ,
WAIT_NODE_TYPE ,
2024-08-16 00:57:21 -07:00
] ;
2024-04-19 02:54:55 -07:00
2024-04-11 03:16:57 -07:00
try {
if ( nodeVersion >= 1.1 ) {
const connectedNodes = this . getParentNodes ( this . getNode ( ) . name ) ;
2024-04-19 02:54:55 -07:00
if ( ! connectedNodes . some ( ( { type } ) = > WEBHOOK_NODE_TYPES . includes ( type ) ) ) {
2024-04-11 03:16:57 -07:00
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” ' ,
} ,
) ;
}
2024-03-28 01:46:39 -07:00
}
2021-11-05 09:45:51 -07:00
2024-04-11 03:16:57 -07:00
const respondWith = this . getNodeParameter ( 'respondWith' , 0 ) as string ;
const options = this . getNodeParameter ( 'options' , 0 , { } ) ;
2021-11-05 09:45:51 -07:00
2024-04-11 03:16:57 -07:00
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 ( ) ;
2021-11-05 09:45:51 -07:00
}
}
2024-04-11 03:16:57 -07:00
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" ,
} ) ;
}
2023-12-21 04:03:26 -08:00
}
2023-09-28 06:46:00 -07:00
}
2024-04-11 03:16:57 -07:00
} 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 ;
} ;
2024-03-28 01:46:39 -07:00
2024-04-11 03:16:57 -07:00
let secretOrPrivateKey ;
2024-03-28 01:46:39 -07:00
2024-04-11 03:16:57 -07:00
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' ,
} ) ;
2024-03-28 01:46:39 -07:00
}
2024-04-11 03:16:57 -07:00
} 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 ] ;
2021-11-05 09:45:51 -07:00
2024-04-11 03:16:57 -07:00
if ( item . binary === undefined ) {
throw new NodeOperationError ( this . getNode ( ) , 'No binary data exists on the first item!' ) ;
}
2021-11-05 09:45:51 -07:00
2024-04-11 03:16:57 -07:00
let responseBinaryPropertyName : string ;
2021-11-05 09:45:51 -07:00
2024-04-11 03:16:57 -07:00
const responseDataSource = this . getNodeParameter ( 'responseDataSource' , 0 ) as string ;
2021-11-05 09:45:51 -07:00
2024-04-11 03:16:57 -07:00
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 ] ;
2021-11-05 09:45:51 -07:00
}
2024-04-11 03:16:57 -07:00
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 ;
}
2021-11-05 09:45:51 -07:00
2024-04-11 03:16:57 -07:00
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! ` ,
) ;
2021-11-05 09:45:51 -07:00
}
2024-04-11 03:16:57 -07:00
const response : IN8nHttpFullResponse = {
body : responseBody ,
headers ,
statusCode ,
} ;
2021-11-05 09:45:51 -07:00
2024-04-11 03:16:57 -07:00
this . sendResponse ( response ) ;
} catch ( error ) {
2024-08-30 00:59:30 -07:00
if ( this . continueOnFail ( ) ) {
2024-04-11 03:16:57 -07:00
const itemData = generatePairedItemData ( items . length ) ;
const returnData = this . helpers . constructExecutionMetaData (
[ { json : { error : error.message } } ] ,
{ itemData } ,
) ;
return [ returnData ] ;
}
throw error ;
}
2021-11-05 09:45:51 -07:00
2023-09-05 03:59:02 -07:00
return [ items ] ;
2021-11-05 09:45:51 -07:00
}
}