2023-03-09 06:38:54 -08:00
import type { Readable } from 'stream' ;
2023-01-27 03:22:44 -08:00
import type {
2023-01-18 05:31:39 -08:00
IBinaryKeyData ,
2022-09-29 14:28:02 -07:00
IDataObject ,
2023-03-06 08:33:32 -08:00
IExecuteFunctions ,
2022-09-29 14:28:02 -07:00
INodeExecutionData ,
INodeType ,
INodeTypeBaseDescription ,
INodeTypeDescription ,
2023-06-29 06:27:02 -07:00
IRequestOptionsSimplified ,
2023-11-01 06:24:43 -07:00
PaginationOptions ,
2023-02-27 19:39:43 -08:00
JsonObject ,
2024-02-14 07:29:09 -08:00
IRequestOptions ,
IHttpRequestMethods ,
2022-09-29 14:28:02 -07:00
} from 'n8n-workflow' ;
2023-03-31 09:31:03 -07:00
2023-07-12 03:31:32 -07:00
import {
BINARY_ENCODING ,
NodeApiError ,
2024-08-29 06:55:53 -07:00
NodeConnectionType ,
2023-07-12 03:31:32 -07:00
NodeOperationError ,
2023-09-20 03:38:34 -07:00
jsonParse ,
2023-07-12 03:31:32 -07:00
removeCircularRefs ,
2023-09-20 03:38:34 -07:00
sleep ,
2023-07-12 03:31:32 -07:00
} from 'n8n-workflow' ;
2024-03-26 06:22:57 -07:00
import set from 'lodash/set' ;
2023-03-31 09:31:03 -07:00
import type { BodyParameter , IAuthDataSanitizeKeys } from '../GenericFunctions' ;
2022-10-20 08:42:55 -07:00
import {
binaryContentTypes ,
getOAuth2AdditionalParameters ,
2024-06-05 00:25:39 -07:00
getSecrets ,
2023-03-31 09:31:03 -07:00
prepareRequestBody ,
2023-09-25 07:59:45 -07:00
reduceAsync ,
2022-10-20 08:42:55 -07:00
replaceNullValues ,
2022-12-23 14:25:59 -08:00
sanitizeUiMessage ,
2024-04-24 07:28:02 -07:00
setAgentOptions ,
2022-10-20 08:42:55 -07:00
} from '../GenericFunctions' ;
2024-06-05 00:25:39 -07:00
import { keysToLowercase } from '@utils/utilities' ;
2024-08-29 06:55:53 -07:00
import { type HttpSslAuthCredentials } from '../interfaces' ;
2023-01-18 05:31:39 -08:00
function toText < T > ( data : T ) {
if ( typeof data === 'object' && data !== null ) {
return JSON . stringify ( data ) ;
}
return data ;
}
2022-09-29 14:28:02 -07:00
export class HttpRequestV3 implements INodeType {
description : INodeTypeDescription ;
constructor ( baseDescription : INodeTypeBaseDescription ) {
this . description = {
. . . baseDescription ,
subtitle : '={{$parameter["method"] + ": " + $parameter["url"]}}' ,
2024-04-10 01:09:20 -07:00
version : [ 3 , 4 , 4.1 , 4.2 ] ,
2022-09-29 14:28:02 -07:00
defaults : {
name : 'HTTP Request' ,
2023-11-01 09:35:47 -07:00
color : '#0004F5' ,
2022-09-29 14:28:02 -07:00
} ,
2024-08-29 06:55:53 -07:00
inputs : [ NodeConnectionType . Main ] ,
outputs : [ NodeConnectionType . Main ] ,
2024-04-24 07:28:02 -07:00
credentials : [
{
name : 'httpSslAuth' ,
required : true ,
displayOptions : {
show : {
provideSslCertificates : [ true ] ,
} ,
} ,
} ,
] ,
2022-09-29 14:28:02 -07:00
properties : [
{
displayName : '' ,
name : 'curlImport' ,
type : 'curlImport' ,
default : '' ,
} ,
{
displayName : 'Method' ,
name : 'method' ,
type : 'options' ,
options : [
{
name : 'DELETE' ,
value : 'DELETE' ,
} ,
{
name : 'GET' ,
value : 'GET' ,
} ,
{
name : 'HEAD' ,
value : 'HEAD' ,
} ,
{
name : 'OPTIONS' ,
value : 'OPTIONS' ,
} ,
{
name : 'PATCH' ,
value : 'PATCH' ,
} ,
{
name : 'POST' ,
value : 'POST' ,
} ,
{
name : 'PUT' ,
value : 'PUT' ,
} ,
] ,
default : 'GET' ,
description : 'The request method to use' ,
} ,
{
displayName : 'URL' ,
name : 'url' ,
type : 'string' ,
default : '' ,
placeholder : 'http://example.com/index.html' ,
description : 'The URL to make the request to' ,
required : true ,
} ,
{
displayName : 'Authentication' ,
name : 'authentication' ,
noDataExpression : true ,
type : 'options' ,
options : [
{
name : 'None' ,
value : 'none' ,
} ,
{
name : 'Predefined Credential Type' ,
value : 'predefinedCredentialType' ,
description :
"We've already implemented auth for many services so that you don't have to set it up manually" ,
} ,
{
name : 'Generic Credential Type' ,
value : 'genericCredentialType' ,
description : 'Fully customizable. Choose between basic, header, OAuth2, etc.' ,
} ,
] ,
default : 'none' ,
} ,
{
displayName : 'Credential Type' ,
name : 'nodeCredentialType' ,
type : 'credentialsSelect' ,
noDataExpression : true ,
required : true ,
default : '' ,
credentialTypes : [ 'extends:oAuth2Api' , 'extends:oAuth1Api' , 'has:authenticate' ] ,
displayOptions : {
show : {
authentication : [ 'predefinedCredentialType' ] ,
} ,
} ,
} ,
2023-04-14 03:39:52 -07:00
{
displayName :
'Make sure you have specified the scope(s) for the Service Account in the credential' ,
name : 'googleApiWarning' ,
type : 'notice' ,
default : '' ,
displayOptions : {
show : {
nodeCredentialType : [ 'googleApi' ] ,
} ,
} ,
} ,
2022-09-29 14:28:02 -07:00
{
displayName : 'Generic Auth Type' ,
name : 'genericAuthType' ,
type : 'credentialsSelect' ,
required : true ,
default : '' ,
credentialTypes : [ 'has:genericAuth' ] ,
displayOptions : {
show : {
authentication : [ 'genericCredentialType' ] ,
} ,
} ,
} ,
2024-04-24 07:28:02 -07:00
{
displayName : 'SSL Certificates' ,
name : 'provideSslCertificates' ,
type : 'boolean' ,
default : false ,
isNodeSetting : true ,
} ,
{
displayName : "Provide certificates in node's 'Credential for SSL Certificates' parameter" ,
name : 'provideSslCertificatesNotice' ,
type : 'notice' ,
default : '' ,
isNodeSetting : true ,
displayOptions : {
show : {
provideSslCertificates : [ true ] ,
} ,
} ,
} ,
{
displayName : 'SSL Certificate' ,
name : 'sslCertificate' ,
type : 'credentials' ,
default : '' ,
displayOptions : {
show : {
provideSslCertificates : [ true ] ,
} ,
} ,
} ,
2022-09-29 14:28:02 -07:00
{
displayName : 'Send Query Parameters' ,
name : 'sendQuery' ,
type : 'boolean' ,
default : false ,
noDataExpression : true ,
description : 'Whether the request has query params or not' ,
} ,
2022-10-21 09:29:20 -07:00
{
displayName : 'Specify Query Parameters' ,
name : 'specifyQuery' ,
type : 'options' ,
displayOptions : {
show : {
sendQuery : [ true ] ,
} ,
} ,
options : [
{
name : 'Using Fields Below' ,
value : 'keypair' ,
} ,
{
name : 'Using JSON' ,
value : 'json' ,
} ,
] ,
default : 'keypair' ,
} ,
2022-09-29 14:28:02 -07:00
{
displayName : 'Query Parameters' ,
name : 'queryParameters' ,
type : 'fixedCollection' ,
displayOptions : {
show : {
sendQuery : [ true ] ,
2022-10-21 09:29:20 -07:00
specifyQuery : [ 'keypair' ] ,
2022-09-29 14:28:02 -07:00
} ,
} ,
typeOptions : {
multipleValues : true ,
} ,
placeholder : 'Add Parameter' ,
default : {
parameters : [
{
name : '' ,
value : '' ,
} ,
] ,
} ,
options : [
{
name : 'parameters' ,
displayName : 'Parameter' ,
values : [
{
displayName : 'Name' ,
name : 'name' ,
type : 'string' ,
default : '' ,
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
default : '' ,
} ,
] ,
} ,
] ,
} ,
2022-10-21 09:29:20 -07:00
{
displayName : 'JSON' ,
name : 'jsonQuery' ,
type : 'json' ,
displayOptions : {
show : {
sendQuery : [ true ] ,
specifyQuery : [ 'json' ] ,
} ,
} ,
default : '' ,
} ,
2022-09-29 14:28:02 -07:00
{
displayName : 'Send Headers' ,
name : 'sendHeaders' ,
type : 'boolean' ,
default : false ,
noDataExpression : true ,
description : 'Whether the request has headers or not' ,
} ,
2022-10-21 09:29:20 -07:00
{
displayName : 'Specify Headers' ,
name : 'specifyHeaders' ,
type : 'options' ,
displayOptions : {
show : {
sendHeaders : [ true ] ,
} ,
} ,
options : [
{
name : 'Using Fields Below' ,
value : 'keypair' ,
} ,
{
name : 'Using JSON' ,
value : 'json' ,
} ,
] ,
default : 'keypair' ,
} ,
2022-09-29 14:28:02 -07:00
{
displayName : 'Header Parameters' ,
name : 'headerParameters' ,
type : 'fixedCollection' ,
displayOptions : {
show : {
sendHeaders : [ true ] ,
2022-10-21 09:29:20 -07:00
specifyHeaders : [ 'keypair' ] ,
2022-09-29 14:28:02 -07:00
} ,
} ,
typeOptions : {
multipleValues : true ,
} ,
placeholder : 'Add Parameter' ,
default : {
parameters : [
{
name : '' ,
value : '' ,
} ,
] ,
} ,
options : [
{
name : 'parameters' ,
displayName : 'Parameter' ,
values : [
{
displayName : 'Name' ,
name : 'name' ,
type : 'string' ,
default : '' ,
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
default : '' ,
} ,
] ,
} ,
] ,
} ,
2022-10-21 09:29:20 -07:00
{
displayName : 'JSON' ,
name : 'jsonHeaders' ,
type : 'json' ,
displayOptions : {
show : {
sendHeaders : [ true ] ,
specifyHeaders : [ 'json' ] ,
} ,
} ,
default : '' ,
} ,
2022-09-29 14:28:02 -07:00
{
displayName : 'Send Body' ,
name : 'sendBody' ,
type : 'boolean' ,
default : false ,
noDataExpression : true ,
description : 'Whether the request has a body or not' ,
} ,
{
displayName : 'Body Content Type' ,
name : 'contentType' ,
type : 'options' ,
displayOptions : {
show : {
sendBody : [ true ] ,
} ,
} ,
options : [
{
name : 'Form Urlencoded' ,
value : 'form-urlencoded' ,
} ,
{
name : 'Form-Data' ,
value : 'multipart-form-data' ,
} ,
{
name : 'JSON' ,
value : 'json' ,
} ,
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
2024-01-03 03:08:16 -08:00
name : 'n8n Binary File' ,
2022-09-29 14:28:02 -07:00
value : 'binaryData' ,
} ,
{
name : 'Raw' ,
value : 'raw' ,
} ,
] ,
default : 'json' ,
description : 'Content-Type to use to send body parameters' ,
} ,
{
displayName : 'Specify Body' ,
name : 'specifyBody' ,
type : 'options' ,
displayOptions : {
show : {
sendBody : [ true ] ,
contentType : [ 'json' ] ,
} ,
} ,
options : [
{
name : 'Using Fields Below' ,
value : 'keypair' ,
} ,
{
name : 'Using JSON' ,
value : 'json' ,
} ,
] ,
default : 'keypair' ,
2023-04-27 08:33:38 -07:00
// eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-json
description :
'The body can be specified using explicit fields (<code>keypair</code>) or using a JavaScript object (<code>json</code>)' ,
2022-09-29 14:28:02 -07:00
} ,
{
displayName : 'Body Parameters' ,
name : 'bodyParameters' ,
type : 'fixedCollection' ,
displayOptions : {
show : {
sendBody : [ true ] ,
contentType : [ 'json' ] ,
specifyBody : [ 'keypair' ] ,
} ,
} ,
typeOptions : {
multipleValues : true ,
} ,
placeholder : 'Add Parameter' ,
default : {
parameters : [
{
name : '' ,
value : '' ,
} ,
] ,
} ,
options : [
{
name : 'parameters' ,
displayName : 'Parameter' ,
values : [
{
displayName : 'Name' ,
name : 'name' ,
type : 'string' ,
default : '' ,
description :
'ID of the field to set. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.' ,
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
default : '' ,
description : 'Value of the field to set' ,
} ,
] ,
} ,
] ,
} ,
{
displayName : 'JSON' ,
name : 'jsonBody' ,
type : 'json' ,
displayOptions : {
show : {
sendBody : [ true ] ,
contentType : [ 'json' ] ,
specifyBody : [ 'json' ] ,
} ,
} ,
default : '' ,
} ,
{
displayName : 'Body Parameters' ,
name : 'bodyParameters' ,
type : 'fixedCollection' ,
displayOptions : {
show : {
sendBody : [ true ] ,
contentType : [ 'multipart-form-data' ] ,
} ,
} ,
typeOptions : {
multipleValues : true ,
} ,
placeholder : 'Add Parameter' ,
default : {
parameters : [
{
name : '' ,
value : '' ,
} ,
] ,
} ,
options : [
{
name : 'parameters' ,
displayName : 'Parameter' ,
values : [
{
displayName : 'Parameter Type' ,
name : 'parameterType' ,
type : 'options' ,
options : [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
2024-01-03 03:08:16 -08:00
name : 'n8n Binary File' ,
2022-09-29 14:28:02 -07:00
value : 'formBinaryData' ,
} ,
{
name : 'Form Data' ,
value : 'formData' ,
} ,
] ,
default : 'formData' ,
} ,
{
displayName : 'Name' ,
name : 'name' ,
type : 'string' ,
default : '' ,
description :
2024-06-20 11:42:08 -07:00
'ID of the field to set. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' ,
2022-09-29 14:28:02 -07:00
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
displayOptions : {
show : {
parameterType : [ 'formData' ] ,
} ,
} ,
default : '' ,
description : 'Value of the field to set' ,
} ,
{
displayName : 'Input Data Field Name' ,
name : 'inputDataFieldName' ,
type : 'string' ,
displayOptions : {
show : {
parameterType : [ 'formBinaryData' ] ,
} ,
} ,
default : '' ,
description :
'The name of the incoming field containing the binary file data to be processed' ,
} ,
] ,
} ,
] ,
} ,
{
displayName : 'Specify Body' ,
name : 'specifyBody' ,
type : 'options' ,
displayOptions : {
show : {
sendBody : [ true ] ,
contentType : [ 'form-urlencoded' ] ,
} ,
} ,
options : [
{
name : 'Using Fields Below' ,
value : 'keypair' ,
} ,
{
name : 'Using Single Field' ,
value : 'string' ,
} ,
] ,
default : 'keypair' ,
} ,
{
displayName : 'Body Parameters' ,
name : 'bodyParameters' ,
type : 'fixedCollection' ,
displayOptions : {
show : {
sendBody : [ true ] ,
contentType : [ 'form-urlencoded' ] ,
specifyBody : [ 'keypair' ] ,
} ,
} ,
typeOptions : {
multipleValues : true ,
} ,
placeholder : 'Add Parameter' ,
default : {
parameters : [
{
name : '' ,
value : '' ,
} ,
] ,
} ,
options : [
{
name : 'parameters' ,
displayName : 'Parameter' ,
values : [
{
displayName : 'Name' ,
name : 'name' ,
type : 'string' ,
default : '' ,
description :
2024-06-20 11:42:08 -07:00
'ID of the field to set. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.' ,
2022-09-29 14:28:02 -07:00
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
default : '' ,
description : 'Value of the field to set' ,
} ,
] ,
} ,
] ,
} ,
{
displayName : 'Body' ,
name : 'body' ,
type : 'string' ,
displayOptions : {
show : {
sendBody : [ true ] ,
specifyBody : [ 'string' ] ,
} ,
} ,
default : '' ,
placeholder : 'field1=value1&field2=value2' ,
} ,
{
displayName : 'Input Data Field Name' ,
name : 'inputDataFieldName' ,
type : 'string' ,
displayOptions : {
show : {
sendBody : [ true ] ,
contentType : [ 'binaryData' ] ,
} ,
} ,
default : '' ,
description :
'The name of the incoming field containing the binary file data to be processed' ,
} ,
{
displayName : 'Content Type' ,
name : 'rawContentType' ,
type : 'string' ,
displayOptions : {
show : {
sendBody : [ true ] ,
contentType : [ 'raw' ] ,
} ,
} ,
default : '' ,
placeholder : 'text/html' ,
} ,
{
displayName : 'Body' ,
name : 'body' ,
type : 'string' ,
displayOptions : {
show : {
sendBody : [ true ] ,
contentType : [ 'raw' ] ,
} ,
} ,
default : '' ,
placeholder : '' ,
} ,
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
2024-07-29 05:27:23 -07:00
placeholder : 'Add option' ,
2022-09-29 14:28:02 -07:00
default : { } ,
options : [
{
displayName : 'Batching' ,
name : 'batching' ,
placeholder : 'Add Batching' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : false ,
} ,
default : {
batch : { } ,
} ,
options : [
{
displayName : 'Batching' ,
name : 'batch' ,
values : [
{
displayName : 'Items per Batch' ,
name : 'batchSize' ,
type : 'number' ,
typeOptions : {
minValue : - 1 ,
} ,
default : 50 ,
description :
'Input will be split in batches to throttle requests. -1 for disabled. 0 will be treated as 1.' ,
} ,
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
displayName : 'Batch Interval (ms)' ,
name : 'batchInterval' ,
type : 'number' ,
typeOptions : {
minValue : 0 ,
} ,
default : 1000 ,
description :
'Time (in milliseconds) between each batch of requests. 0 for disabled.' ,
} ,
] ,
} ,
] ,
} ,
{
displayName : 'Ignore SSL Issues' ,
name : 'allowUnauthorizedCerts' ,
type : 'boolean' ,
noDataExpression : true ,
default : false ,
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-ignore-ssl-issues
description :
'Whether to download the response even if SSL certificate validation is not possible' ,
} ,
{
displayName : 'Array Format in Query Parameters' ,
name : 'queryParameterArrays' ,
type : 'options' ,
displayOptions : {
show : {
'/sendQuery' : [ true ] ,
} ,
} ,
options : [
{
name : 'No Brackets' ,
value : 'repeat' ,
// eslint-disable-next-line n8n-nodes-base/node-param-description-lowercase-first-char
description : 'e.g. foo=bar&foo=qux' ,
} ,
{
name : 'Brackets Only' ,
value : 'brackets' ,
// eslint-disable-next-line n8n-nodes-base/node-param-description-lowercase-first-char
description : 'e.g. foo[]=bar&foo[]=qux' ,
} ,
{
name : 'Brackets with Indices' ,
value : 'indices' ,
// eslint-disable-next-line n8n-nodes-base/node-param-description-lowercase-first-char
description : 'e.g. foo[0]=bar&foo[1]=qux' ,
} ,
] ,
default : 'brackets' ,
} ,
2024-07-29 00:32:42 -07:00
{
displayName : 'Lowercase Headers' ,
name : 'lowercaseHeaders' ,
type : 'boolean' ,
default : true ,
description : 'Whether to lowercase header names' ,
} ,
2023-04-05 06:37:14 -07:00
{
displayName : 'Redirects' ,
name : 'redirect' ,
placeholder : 'Add Redirect' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : false ,
} ,
default : { redirect : { } } ,
options : [
{
displayName : 'Redirect' ,
name : 'redirect' ,
values : [
{
displayName : 'Follow Redirects' ,
name : 'followRedirects' ,
type : 'boolean' ,
default : false ,
noDataExpression : true ,
description : 'Whether to follow all redirects' ,
} ,
{
displayName : 'Max Redirects' ,
name : 'maxRedirects' ,
type : 'number' ,
displayOptions : {
show : {
followRedirects : [ true ] ,
} ,
} ,
default : 21 ,
description : 'Max number of redirects to follow' ,
} ,
] ,
} ,
] ,
displayOptions : {
show : {
'@version' : [ 1 , 2 , 3 ] ,
} ,
} ,
} ,
2022-09-29 14:28:02 -07:00
{
displayName : 'Redirects' ,
name : 'redirect' ,
placeholder : 'Add Redirect' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : false ,
} ,
default : {
redirect : { } ,
} ,
options : [
{
displayName : 'Redirect' ,
name : 'redirect' ,
values : [
{
displayName : 'Follow Redirects' ,
name : 'followRedirects' ,
type : 'boolean' ,
2023-04-05 06:37:14 -07:00
default : true ,
2022-09-29 14:28:02 -07:00
noDataExpression : true ,
description : 'Whether to follow all redirects' ,
} ,
{
displayName : 'Max Redirects' ,
name : 'maxRedirects' ,
type : 'number' ,
displayOptions : {
show : {
followRedirects : [ true ] ,
} ,
} ,
default : 21 ,
description : 'Max number of redirects to follow' ,
} ,
] ,
} ,
] ,
2023-04-05 06:37:14 -07:00
displayOptions : {
hide : {
'@version' : [ 1 , 2 , 3 ] ,
} ,
} ,
2022-09-29 14:28:02 -07:00
} ,
{
displayName : 'Response' ,
name : 'response' ,
placeholder : 'Add response' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : false ,
} ,
default : {
response : { } ,
} ,
options : [
{
displayName : 'Response' ,
name : 'response' ,
values : [
{
displayName : 'Include Response Headers and Status' ,
name : 'fullResponse' ,
type : 'boolean' ,
default : false ,
description :
2023-03-03 09:49:19 -08:00
'Whether to return the full response (headers and response status code) data instead of only the body' ,
2022-09-29 14:28:02 -07:00
} ,
{
displayName : 'Never Error' ,
name : 'neverError' ,
type : 'boolean' ,
default : false ,
description : 'Whether to succeeds also when status code is not 2xx' ,
} ,
{
displayName : 'Response Format' ,
name : 'responseFormat' ,
type : 'options' ,
noDataExpression : true ,
options : [
{
name : 'Autodetect' ,
value : 'autodetect' ,
} ,
{
name : 'File' ,
value : 'file' ,
} ,
{
name : 'JSON' ,
value : 'json' ,
} ,
{
name : 'Text' ,
value : 'text' ,
} ,
] ,
default : 'autodetect' ,
description : 'The format in which the data gets returned from the URL' ,
} ,
{
displayName : 'Put Output in Field' ,
name : 'outputPropertyName' ,
type : 'string' ,
default : 'data' ,
required : true ,
displayOptions : {
show : {
responseFormat : [ 'file' , 'text' ] ,
} ,
} ,
description :
'Name of the binary property to which to write the data of the read file' ,
} ,
] ,
} ,
] ,
} ,
2023-11-01 06:24:43 -07:00
{
displayName : 'Pagination' ,
name : 'pagination' ,
placeholder : 'Add pagination' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : false ,
} ,
default : {
pagination : { } ,
} ,
options : [
{
displayName : 'Pagination' ,
name : 'pagination' ,
values : [
{
displayName : 'Pagination Mode' ,
name : 'paginationMode' ,
type : 'options' ,
typeOptions : {
noDataExpression : true ,
} ,
options : [
{
name : 'Off' ,
value : 'off' ,
} ,
{
name : 'Update a Parameter in Each Request' ,
value : 'updateAParameterInEachRequest' ,
} ,
{
name : 'Response Contains Next URL' ,
value : 'responseContainsNextURL' ,
} ,
] ,
default : 'updateAParameterInEachRequest' ,
description : 'If pagination should be used' ,
} ,
{
displayName :
2024-06-20 11:42:08 -07:00
'Use the $response variables to access the data of the previous response. Refer to the <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/#pagination/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.httpRequest" target="_blank">docs</a> for more info about pagination/' ,
2023-11-01 06:24:43 -07:00
name : 'webhookNotice' ,
displayOptions : {
hide : {
paginationMode : [ 'off' ] ,
} ,
} ,
type : 'notice' ,
default : '' ,
} ,
{
displayName : 'Next URL' ,
name : 'nextURL' ,
type : 'string' ,
displayOptions : {
show : {
paginationMode : [ 'responseContainsNextURL' ] ,
} ,
} ,
default : '' ,
description :
2024-02-12 08:32:27 -08:00
'Should evaluate to the URL of the next page. <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/#pagination" target="_blank">More info</a>.' ,
2023-11-01 06:24:43 -07:00
} ,
{
displayName : 'Parameters' ,
name : 'parameters' ,
type : 'fixedCollection' ,
displayOptions : {
show : {
paginationMode : [ 'updateAParameterInEachRequest' ] ,
} ,
} ,
typeOptions : {
multipleValues : true ,
noExpression : true ,
} ,
placeholder : 'Add Parameter' ,
default : {
parameters : [
{
type : 'qs' ,
name : '' ,
value : '' ,
} ,
] ,
} ,
options : [
{
name : 'parameters' ,
displayName : 'Parameter' ,
values : [
{
displayName : 'Type' ,
name : 'type' ,
type : 'options' ,
options : [
{
name : 'Body' ,
value : 'body' ,
} ,
{
name : 'Header' ,
value : 'headers' ,
} ,
{
name : 'Query' ,
value : 'qs' ,
} ,
] ,
default : 'qs' ,
description : 'Where the parameter should be set' ,
} ,
{
displayName : 'Name' ,
name : 'name' ,
type : 'string' ,
default : '' ,
2024-02-01 02:35:39 -08:00
placeholder : 'e.g page' ,
2023-11-01 06:24:43 -07:00
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
default : '' ,
2024-02-01 02:35:39 -08:00
hint : 'Use expression mode and $response to access response data' ,
2023-11-01 06:24:43 -07:00
} ,
] ,
} ,
] ,
} ,
{
displayName : 'Pagination Complete When' ,
name : 'paginationCompleteWhen' ,
type : 'options' ,
typeOptions : {
noDataExpression : true ,
} ,
displayOptions : {
hide : {
paginationMode : [ 'off' ] ,
} ,
} ,
options : [
{
name : 'Response Is Empty' ,
value : 'responseIsEmpty' ,
} ,
{
name : 'Receive Specific Status Code(s)' ,
value : 'receiveSpecificStatusCodes' ,
} ,
{
name : 'Other' ,
value : 'other' ,
} ,
] ,
default : 'responseIsEmpty' ,
description : 'When should no further requests be made?' ,
} ,
{
displayName : 'Status Code(s) when Complete' ,
name : 'statusCodesWhenComplete' ,
type : 'string' ,
typeOptions : {
noDataExpression : true ,
} ,
displayOptions : {
show : {
paginationCompleteWhen : [ 'receiveSpecificStatusCodes' ] ,
} ,
} ,
default : '' ,
description : 'Accepts comma-separated values' ,
} ,
{
displayName : 'Complete Expression' ,
name : 'completeExpression' ,
type : 'string' ,
displayOptions : {
show : {
paginationCompleteWhen : [ 'other' ] ,
} ,
} ,
default : '' ,
description :
2024-02-12 08:32:27 -08:00
'Should evaluate to true when pagination is complete. <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/#pagination" target="_blank">More info</a>.' ,
2023-11-01 06:24:43 -07:00
} ,
{
displayName : 'Limit Pages Fetched' ,
name : 'limitPagesFetched' ,
type : 'boolean' ,
typeOptions : {
noDataExpression : true ,
} ,
displayOptions : {
hide : {
paginationMode : [ 'off' ] ,
} ,
} ,
default : false ,
noDataExpression : true ,
description : 'Whether the number of requests should be limited' ,
} ,
{
displayName : 'Max Pages' ,
name : 'maxRequests' ,
type : 'number' ,
typeOptions : {
noDataExpression : true ,
} ,
displayOptions : {
show : {
limitPagesFetched : [ true ] ,
} ,
} ,
default : 100 ,
description : 'Maximum amount of request to be make' ,
} ,
2024-01-04 07:11:16 -08:00
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
displayName : 'Interval Between Requests (ms)' ,
name : 'requestInterval' ,
type : 'number' ,
displayOptions : {
hide : {
paginationMode : [ 'off' ] ,
} ,
} ,
default : 0 ,
description : 'Time in milliseconds to wait between requests' ,
hint : 'At 0 no delay will be added' ,
typeOptions : {
minValue : 0 ,
} ,
} ,
2023-11-01 06:24:43 -07:00
] ,
} ,
] ,
} ,
2022-09-29 14:28:02 -07:00
{
displayName : 'Proxy' ,
name : 'proxy' ,
type : 'string' ,
default : '' ,
placeholder : 'e.g. http://myproxy:3128' ,
description : 'HTTP proxy to use' ,
} ,
{
displayName : 'Timeout' ,
name : 'timeout' ,
type : 'number' ,
typeOptions : {
minValue : 1 ,
} ,
default : 10000 ,
description :
'Time in ms to wait for the server to send response headers (and start the response body) before aborting the request' ,
} ,
] ,
} ,
2023-06-22 06:30:04 -07:00
{
displayName :
"You can view the raw requests this node makes in your browser's developer console" ,
name : 'infoMessage' ,
type : 'notice' ,
default : '' ,
} ,
2022-09-29 14:28:02 -07:00
] ,
} ;
}
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
const items = this . getInputData ( ) ;
2023-03-31 09:31:03 -07:00
const nodeVersion = this . getNode ( ) . typeVersion ;
2022-09-29 14:28:02 -07:00
2023-03-03 09:49:19 -08:00
const fullResponseProperties = [ 'body' , 'headers' , 'statusCode' , 'statusMessage' ] ;
2022-09-29 14:28:02 -07:00
let authentication ;
try {
authentication = this . getNodeParameter ( 'authentication' , 0 ) as
| 'predefinedCredentialType'
| 'genericCredentialType'
| 'none' ;
2023-03-03 09:18:49 -08:00
} catch { }
2022-09-29 14:28:02 -07:00
let httpBasicAuth ;
let httpDigestAuth ;
let httpHeaderAuth ;
let httpQueryAuth ;
2023-06-29 06:27:02 -07:00
let httpCustomAuth ;
2022-09-29 14:28:02 -07:00
let oAuth1Api ;
let oAuth2Api ;
2024-04-24 07:28:02 -07:00
let sslCertificates ;
2023-11-15 00:13:33 -08:00
let nodeCredentialType : string | undefined ;
let genericCredentialType : string | undefined ;
2022-09-29 14:28:02 -07:00
2024-02-14 07:29:09 -08:00
let requestOptions : IRequestOptions = {
2022-09-29 14:28:02 -07:00
uri : '' ,
} ;
let returnItems : INodeExecutionData [ ] = [ ] ;
const requestPromises = [ ] ;
let fullResponse = false ;
let autoDetectResponseFormat = false ;
2023-11-01 06:24:43 -07:00
// Can not be defined on a per item level
const pagination = this . getNodeParameter ( 'options.pagination.pagination' , 0 , null , {
rawExpressions : true ,
} ) as {
paginationMode : 'off' | 'updateAParameterInEachRequest' | 'responseContainsNextURL' ;
nextURL? : string ;
parameters : {
parameters : Array < {
type : 'body' | 'headers' | 'qs' ;
name : string ;
value : string ;
} > ;
} ;
paginationCompleteWhen : 'responseIsEmpty' | 'receiveSpecificStatusCodes' | 'other' ;
statusCodesWhenComplete : string ;
completeExpression : string ;
limitPagesFetched : boolean ;
maxRequests : number ;
2024-01-04 07:11:16 -08:00
requestInterval : number ;
2023-11-01 06:24:43 -07:00
} ;
2024-06-05 00:25:39 -07:00
const requests : Array < {
options : IRequestOptions ;
authKeys : IAuthDataSanitizeKeys ;
credentialType? : string ;
} > = [ ] ;
2022-09-29 14:28:02 -07:00
for ( let itemIndex = 0 ; itemIndex < items . length ; itemIndex ++ ) {
2024-01-08 01:48:20 -08:00
if ( authentication === 'genericCredentialType' ) {
genericCredentialType = this . getNodeParameter ( 'genericAuthType' , 0 ) as string ;
if ( genericCredentialType === 'httpBasicAuth' ) {
2024-01-16 09:25:53 -08:00
httpBasicAuth = await this . getCredentials ( 'httpBasicAuth' , itemIndex ) ;
2024-01-08 01:48:20 -08:00
} else if ( genericCredentialType === 'httpDigestAuth' ) {
2024-01-16 09:25:53 -08:00
httpDigestAuth = await this . getCredentials ( 'httpDigestAuth' , itemIndex ) ;
2024-01-08 01:48:20 -08:00
} else if ( genericCredentialType === 'httpHeaderAuth' ) {
2024-01-16 09:25:53 -08:00
httpHeaderAuth = await this . getCredentials ( 'httpHeaderAuth' , itemIndex ) ;
2024-01-08 01:48:20 -08:00
} else if ( genericCredentialType === 'httpQueryAuth' ) {
2024-01-16 09:25:53 -08:00
httpQueryAuth = await this . getCredentials ( 'httpQueryAuth' , itemIndex ) ;
2024-01-08 01:48:20 -08:00
} else if ( genericCredentialType === 'httpCustomAuth' ) {
2024-01-16 09:25:53 -08:00
httpCustomAuth = await this . getCredentials ( 'httpCustomAuth' , itemIndex ) ;
2024-01-08 01:48:20 -08:00
} else if ( genericCredentialType === 'oAuth1Api' ) {
2024-01-16 09:25:53 -08:00
oAuth1Api = await this . getCredentials ( 'oAuth1Api' , itemIndex ) ;
2024-01-08 01:48:20 -08:00
} else if ( genericCredentialType === 'oAuth2Api' ) {
2024-01-16 09:25:53 -08:00
oAuth2Api = await this . getCredentials ( 'oAuth2Api' , itemIndex ) ;
2024-01-08 01:48:20 -08:00
}
} else if ( authentication === 'predefinedCredentialType' ) {
2024-01-31 04:12:18 -08:00
nodeCredentialType = this . getNodeParameter ( 'nodeCredentialType' , itemIndex ) as string ;
2024-01-08 01:48:20 -08:00
}
2024-04-24 07:28:02 -07:00
const provideSslCertificates = this . getNodeParameter (
'provideSslCertificates' ,
itemIndex ,
false ,
) ;
if ( provideSslCertificates ) {
sslCertificates = ( await this . getCredentials (
'httpSslAuth' ,
itemIndex ,
) ) as HttpSslAuthCredentials ;
}
2024-02-14 07:29:09 -08:00
const requestMethod = this . getNodeParameter ( 'method' , itemIndex ) as IHttpRequestMethods ;
2022-09-29 14:28:02 -07:00
const sendQuery = this . getNodeParameter ( 'sendQuery' , itemIndex , false ) as boolean ;
const queryParameters = this . getNodeParameter (
'queryParameters.parameters' ,
itemIndex ,
[ ] ,
) as [ { name : string ; value : string } ] ;
2022-10-21 09:29:20 -07:00
const specifyQuery = this . getNodeParameter ( 'specifyQuery' , itemIndex , 'keypair' ) as string ;
const jsonQueryParameter = this . getNodeParameter ( 'jsonQuery' , itemIndex , '' ) as string ;
2022-09-29 14:28:02 -07:00
const sendBody = this . getNodeParameter ( 'sendBody' , itemIndex , false ) as boolean ;
const bodyContentType = this . getNodeParameter ( 'contentType' , itemIndex , '' ) as string ;
const specifyBody = this . getNodeParameter ( 'specifyBody' , itemIndex , '' ) as string ;
2023-03-31 09:31:03 -07:00
const bodyParameters = this . getNodeParameter (
'bodyParameters.parameters' ,
itemIndex ,
[ ] ,
) as BodyParameter [ ] ;
2022-09-29 14:28:02 -07:00
const jsonBodyParameter = this . getNodeParameter ( 'jsonBody' , itemIndex , '' ) as string ;
const body = this . getNodeParameter ( 'body' , itemIndex , '' ) as string ;
const sendHeaders = this . getNodeParameter ( 'sendHeaders' , itemIndex , false ) as boolean ;
2023-04-27 03:36:02 -07:00
2022-09-29 14:28:02 -07:00
const headerParameters = this . getNodeParameter (
'headerParameters.parameters' ,
itemIndex ,
[ ] ,
) as [ { name : string ; value : string } ] ;
2023-04-27 03:36:02 -07:00
2022-10-21 11:52:43 -07:00
const specifyHeaders = this . getNodeParameter (
'specifyHeaders' ,
itemIndex ,
'keypair' ,
) as string ;
2023-04-27 03:36:02 -07:00
2022-10-21 09:29:20 -07:00
const jsonHeadersParameter = this . getNodeParameter ( 'jsonHeaders' , itemIndex , '' ) as string ;
2023-04-27 03:36:02 -07:00
2022-09-29 14:28:02 -07:00
const {
redirect ,
batching ,
proxy ,
timeout ,
allowUnauthorizedCerts ,
queryParameterArrays ,
response ,
2024-07-29 00:32:42 -07:00
lowercaseHeaders ,
2022-09-29 14:28:02 -07:00
} = this . getNodeParameter ( 'options' , itemIndex , { } ) as {
batching : { batch : { batchSize : number ; batchInterval : number } } ;
proxy : string ;
timeout : number ;
allowUnauthorizedCerts : boolean ;
queryParameterArrays : 'indices' | 'brackets' | 'repeat' ;
response : {
response : { neverError : boolean ; responseFormat : string ; fullResponse : boolean } ;
} ;
redirect : { redirect : { maxRedirects : number ; followRedirects : boolean } } ;
2024-07-29 00:32:42 -07:00
lowercaseHeaders : boolean ;
2022-09-29 14:28:02 -07:00
} ;
const url = this . getNodeParameter ( 'url' , itemIndex ) as string ;
const responseFormat = response ? . response ? . responseFormat || 'autodetect' ;
fullResponse = response ? . response ? . fullResponse || false ;
autoDetectResponseFormat = responseFormat === 'autodetect' ;
// defaults batch size to 1 of it's set to 0
const batchSize = batching ? . batch ? . batchSize > 0 ? batching?.batch?.batchSize : 1 ;
const batchInterval = batching ? . batch . batchInterval ;
if ( itemIndex > 0 && batchSize >= 0 && batchInterval > 0 ) {
if ( itemIndex % batchSize === 0 ) {
2022-11-08 08:06:00 -08:00
await sleep ( batchInterval ) ;
2022-09-29 14:28:02 -07:00
}
}
requestOptions = {
headers : { } ,
method : requestMethod ,
uri : url ,
gzip : true ,
rejectUnauthorized : ! allowUnauthorizedCerts || false ,
followRedirect : false ,
2023-11-01 06:24:43 -07:00
resolveWithFullResponse : true ,
2022-09-29 14:28:02 -07:00
} ;
2023-05-04 00:55:35 -07:00
if ( requestOptions . method !== 'GET' && nodeVersion >= 4.1 ) {
requestOptions = { . . . requestOptions , followAllRedirects : false } ;
}
2023-04-05 06:37:14 -07:00
const defaultRedirect = nodeVersion >= 4 && redirect === undefined ;
2022-09-29 14:28:02 -07:00
2023-04-05 06:37:14 -07:00
if ( redirect ? . redirect ? . followRedirects || defaultRedirect ) {
2022-09-29 14:28:02 -07:00
requestOptions . followRedirect = true ;
requestOptions . followAllRedirects = true ;
}
2023-04-05 06:37:14 -07:00
if ( redirect ? . redirect ? . maxRedirects || defaultRedirect ) {
2022-09-29 14:28:02 -07:00
requestOptions . maxRedirects = redirect ? . redirect ? . maxRedirects ;
}
2022-12-02 12:54:28 -08:00
if ( response ? . response ? . neverError ) {
2022-09-29 14:28:02 -07:00
requestOptions . simple = false ;
}
if ( proxy ) {
requestOptions . proxy = proxy ;
}
if ( timeout ) {
requestOptions . timeout = timeout ;
} else {
2023-09-20 03:38:34 -07:00
// set default timeout to 5 minutes
requestOptions . timeout = 300 _000 ;
2022-09-29 14:28:02 -07:00
}
if ( sendQuery && queryParameterArrays ) {
Object . assign ( requestOptions , {
qsStringifyOptions : { arrayFormat : queryParameterArrays } ,
} ) ;
}
2023-09-25 07:59:45 -07:00
const parametersToKeyValue = async (
2023-03-09 06:38:54 -08:00
accumulator : { [ key : string ] : any } ,
2022-09-29 14:28:02 -07:00
cur : { name : string ; value : string ; parameterType? : string ; inputDataFieldName? : string } ,
) = > {
if ( cur . parameterType === 'formBinaryData' ) {
2023-03-03 09:49:19 -08:00
if ( ! cur . inputDataFieldName ) return accumulator ;
2023-03-06 08:33:32 -08:00
const binaryData = this . helpers . assertBinaryData ( itemIndex , cur . inputDataFieldName ) ;
2023-03-09 06:38:54 -08:00
let uploadData : Buffer | Readable ;
const itemBinaryData = items [ itemIndex ] . binary ! [ cur . inputDataFieldName ] ;
if ( itemBinaryData . id ) {
2023-09-25 07:59:45 -07:00
uploadData = await this . helpers . getBinaryStream ( itemBinaryData . id ) ;
2023-03-09 06:38:54 -08:00
} else {
uploadData = Buffer . from ( itemBinaryData . data , BINARY_ENCODING ) ;
}
2022-09-29 14:28:02 -07:00
2023-03-03 09:49:19 -08:00
accumulator [ cur . name ] = {
2023-03-09 06:38:54 -08:00
value : uploadData ,
2022-09-29 14:28:02 -07:00
options : {
filename : binaryData.fileName ,
contentType : binaryData.mimeType ,
} ,
} ;
2023-03-03 09:49:19 -08:00
return accumulator ;
2022-09-29 14:28:02 -07:00
}
2023-03-03 09:49:19 -08:00
accumulator [ cur . name ] = cur . value ;
return accumulator ;
2022-09-29 14:28:02 -07:00
} ;
// Get parameters defined in the UI
if ( sendBody && bodyParameters ) {
if ( specifyBody === 'keypair' || bodyContentType === 'multipart-form-data' ) {
2023-09-25 07:59:45 -07:00
requestOptions . body = await prepareRequestBody (
2023-03-31 09:31:03 -07:00
bodyParameters ,
bodyContentType ,
nodeVersion ,
parametersToKeyValue ,
) ;
2022-09-29 14:28:02 -07:00
} else if ( specifyBody === 'json' ) {
// body is specified using JSON
2023-01-19 01:34:36 -08:00
if ( typeof jsonBodyParameter !== 'object' && jsonBodyParameter !== null ) {
try {
JSON . parse ( jsonBodyParameter ) ;
2023-03-03 09:18:49 -08:00
} catch {
2023-01-19 01:34:36 -08:00
throw new NodeOperationError (
this . getNode ( ) ,
'JSON parameter need to be an valid JSON' ,
{
2023-04-18 02:48:38 -07:00
itemIndex ,
2023-01-19 01:34:36 -08:00
} ,
) ;
}
2022-09-29 14:28:02 -07:00
2023-01-19 01:34:36 -08:00
requestOptions . body = jsonParse ( jsonBodyParameter ) ;
} else {
requestOptions . body = jsonBodyParameter ;
}
2022-09-29 14:28:02 -07:00
} else if ( specifyBody === 'string' ) {
//form urlencoded
requestOptions . body = Object . fromEntries ( new URLSearchParams ( body ) ) ;
}
}
// Change the way data get send in case a different content-type than JSON got selected
if ( sendBody && [ 'PATCH' , 'POST' , 'PUT' , 'GET' ] . includes ( requestMethod ) ) {
if ( bodyContentType === 'multipart-form-data' ) {
2024-02-14 07:29:09 -08:00
requestOptions . formData = requestOptions . body as IDataObject ;
2022-09-29 14:28:02 -07:00
delete requestOptions . body ;
} else if ( bodyContentType === 'form-urlencoded' ) {
2024-02-14 07:29:09 -08:00
requestOptions . form = requestOptions . body as IDataObject ;
2022-09-29 14:28:02 -07:00
delete requestOptions . body ;
} else if ( bodyContentType === 'binaryData' ) {
const inputDataFieldName = this . getNodeParameter (
'inputDataFieldName' ,
itemIndex ,
) as string ;
2023-03-09 06:38:54 -08:00
let uploadData : Buffer | Readable ;
let contentLength : number ;
const itemBinaryData = this . helpers . assertBinaryData ( itemIndex , inputDataFieldName ) ;
if ( itemBinaryData . id ) {
2023-09-25 07:59:45 -07:00
uploadData = await this . helpers . getBinaryStream ( itemBinaryData . id ) ;
2023-03-09 06:38:54 -08:00
const metadata = await this . helpers . getBinaryMetadata ( itemBinaryData . id ) ;
contentLength = metadata . fileSize ;
} else {
uploadData = Buffer . from ( itemBinaryData . data , BINARY_ENCODING ) ;
contentLength = uploadData . length ;
}
requestOptions . body = uploadData ;
requestOptions . headers = {
. . . requestOptions . headers ,
2023-04-27 03:36:02 -07:00
'content-length' : contentLength ,
'content-type' : itemBinaryData . mimeType ? ? 'application/octet-stream' ,
2023-03-09 06:38:54 -08:00
} ;
2022-09-29 14:28:02 -07:00
} else if ( bodyContentType === 'raw' ) {
requestOptions . body = body ;
}
}
2022-10-21 09:29:20 -07:00
// Get parameters defined in the UI
2022-09-29 14:28:02 -07:00
if ( sendQuery && queryParameters ) {
2022-10-21 09:29:20 -07:00
if ( specifyQuery === 'keypair' ) {
2023-09-25 07:59:45 -07:00
requestOptions . qs = await reduceAsync ( queryParameters , parametersToKeyValue ) ;
2022-10-21 09:29:20 -07:00
} else if ( specifyQuery === 'json' ) {
// query is specified using JSON
try {
JSON . parse ( jsonQueryParameter ) ;
2023-03-03 09:18:49 -08:00
} catch {
2022-10-21 09:29:20 -07:00
throw new NodeOperationError (
this . getNode ( ) ,
2022-12-29 03:20:43 -08:00
'JSON parameter need to be an valid JSON' ,
2022-10-21 09:29:20 -07:00
{
2023-04-18 02:48:38 -07:00
itemIndex ,
2022-10-21 09:29:20 -07:00
} ,
) ;
}
2022-10-21 11:52:43 -07:00
requestOptions . qs = jsonParse ( jsonQueryParameter ) ;
2022-10-21 09:29:20 -07:00
}
2022-09-29 14:28:02 -07:00
}
2022-10-21 09:29:20 -07:00
// Get parameters defined in the UI
2022-09-29 14:28:02 -07:00
if ( sendHeaders && headerParameters ) {
2023-04-27 03:36:02 -07:00
let additionalHeaders : IDataObject = { } ;
2022-10-21 09:29:20 -07:00
if ( specifyHeaders === 'keypair' ) {
2024-04-17 00:45:38 -07:00
additionalHeaders = await reduceAsync (
headerParameters . filter ( ( header ) = > header . name ) ,
parametersToKeyValue ,
) ;
2022-10-21 09:29:20 -07:00
} else if ( specifyHeaders === 'json' ) {
// body is specified using JSON
try {
JSON . parse ( jsonHeadersParameter ) ;
2023-03-03 09:18:49 -08:00
} catch {
2022-10-21 09:29:20 -07:00
throw new NodeOperationError (
this . getNode ( ) ,
2022-12-29 03:20:43 -08:00
'JSON parameter need to be an valid JSON' ,
2022-10-21 09:29:20 -07:00
{
2023-04-18 02:48:38 -07:00
itemIndex ,
2022-10-21 09:29:20 -07:00
} ,
) ;
}
2023-04-27 03:36:02 -07:00
additionalHeaders = jsonParse ( jsonHeadersParameter ) ;
2022-10-21 09:29:20 -07:00
}
2023-04-27 03:36:02 -07:00
requestOptions . headers = {
. . . requestOptions . headers ,
2024-07-29 00:32:42 -07:00
. . . ( lowercaseHeaders === undefined || lowercaseHeaders
? keysToLowercase ( additionalHeaders )
: additionalHeaders ) ,
2023-04-27 03:36:02 -07:00
} ;
2022-09-29 14:28:02 -07:00
}
if ( autoDetectResponseFormat || responseFormat === 'file' ) {
requestOptions . encoding = null ;
requestOptions . json = false ;
2023-03-21 07:03:47 -07:00
requestOptions . useStream = true ;
2022-09-29 14:28:02 -07:00
} else if ( bodyContentType === 'raw' ) {
requestOptions . json = false ;
2023-03-21 07:03:47 -07:00
requestOptions . useStream = true ;
2022-09-29 14:28:02 -07:00
} else {
requestOptions . json = true ;
}
2023-11-01 06:24:43 -07:00
// Add Content Type if any are set
2022-09-29 14:28:02 -07:00
if ( bodyContentType === 'raw' ) {
if ( requestOptions . headers === undefined ) {
requestOptions . headers = { } ;
}
const rawContentType = this . getNodeParameter ( 'rawContentType' , itemIndex ) as string ;
2023-04-27 03:36:02 -07:00
requestOptions . headers [ 'content-type' ] = rawContentType ;
2022-09-29 14:28:02 -07:00
}
2022-12-23 14:25:59 -08:00
const authDataKeys : IAuthDataSanitizeKeys = { } ;
2024-04-24 07:28:02 -07:00
// Add SSL certificates if any are set
setAgentOptions ( requestOptions , sslCertificates ) ;
if ( requestOptions . agentOptions ) {
authDataKeys . agentOptions = Object . keys ( requestOptions . agentOptions ) ;
}
2022-09-29 14:28:02 -07:00
// Add credentials if any are set
if ( httpBasicAuth !== undefined ) {
requestOptions . auth = {
user : httpBasicAuth.user as string ,
pass : httpBasicAuth.password as string ,
} ;
2022-12-23 14:25:59 -08:00
authDataKeys . auth = [ 'pass' ] ;
2022-09-29 14:28:02 -07:00
}
if ( httpHeaderAuth !== undefined ) {
requestOptions . headers ! [ httpHeaderAuth . name as string ] = httpHeaderAuth . value ;
2022-12-23 14:25:59 -08:00
authDataKeys . headers = [ httpHeaderAuth . name as string ] ;
2022-09-29 14:28:02 -07:00
}
if ( httpQueryAuth !== undefined ) {
if ( ! requestOptions . qs ) {
requestOptions . qs = { } ;
}
2022-12-02 12:54:28 -08:00
requestOptions . qs [ httpQueryAuth . name as string ] = httpQueryAuth . value ;
2022-12-23 14:25:59 -08:00
authDataKeys . qs = [ httpQueryAuth . name as string ] ;
2022-09-29 14:28:02 -07:00
}
2024-04-24 07:28:02 -07:00
2022-09-29 14:28:02 -07:00
if ( httpDigestAuth !== undefined ) {
requestOptions . auth = {
user : httpDigestAuth.user as string ,
pass : httpDigestAuth.password as string ,
sendImmediately : false ,
} ;
2022-12-23 14:25:59 -08:00
authDataKeys . auth = [ 'pass' ] ;
2022-09-29 14:28:02 -07:00
}
2023-06-29 06:27:02 -07:00
if ( httpCustomAuth !== undefined ) {
const customAuth = jsonParse < IRequestOptionsSimplified > (
( httpCustomAuth . json as string ) || '{}' ,
{ errorMessage : 'Invalid Custom Auth JSON' } ,
) ;
if ( customAuth . headers ) {
requestOptions . headers = { . . . requestOptions . headers , . . . customAuth . headers } ;
authDataKeys . headers = Object . keys ( customAuth . headers ) ;
}
if ( customAuth . body ) {
2024-02-14 07:29:09 -08:00
requestOptions . body = { . . . ( requestOptions . body as IDataObject ) , . . . customAuth . body } ;
2023-06-29 06:27:02 -07:00
authDataKeys . body = Object . keys ( customAuth . body ) ;
}
if ( customAuth . qs ) {
requestOptions . qs = { . . . requestOptions . qs , . . . customAuth . qs } ;
authDataKeys . qs = Object . keys ( customAuth . qs ) ;
}
}
2022-09-29 14:28:02 -07:00
2022-12-02 12:54:28 -08:00
if ( requestOptions . headers ! . accept === undefined ) {
2022-09-29 14:28:02 -07:00
if ( responseFormat === 'json' ) {
2022-12-02 12:54:28 -08:00
requestOptions . headers ! . accept = 'application/json,text/*;q=0.99' ;
2022-09-29 14:28:02 -07:00
} else if ( responseFormat === 'text' ) {
2022-12-02 12:54:28 -08:00
requestOptions . headers ! . accept =
2022-09-29 14:28:02 -07:00
'application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, */*;q=0.1' ;
} else {
2022-12-02 12:54:28 -08:00
requestOptions . headers ! . accept =
2022-09-29 14:28:02 -07:00
'application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7' ;
}
}
2024-03-07 08:08:01 -08:00
2024-06-05 00:25:39 -07:00
requests . push ( {
options : requestOptions ,
authKeys : authDataKeys ,
credentialType : nodeCredentialType ,
} ) ;
2023-11-01 06:24:43 -07:00
if ( pagination && pagination . paginationMode !== 'off' ) {
let continueExpression = '={{false}}' ;
if ( pagination . paginationCompleteWhen === 'receiveSpecificStatusCodes' ) {
// Split out comma separated list of status codes into array
const statusCodesWhenCompleted = pagination . statusCodesWhenComplete
. split ( ',' )
. map ( ( item ) = > parseInt ( item . trim ( ) ) ) ;
continueExpression = ` ={{ ! ${ JSON . stringify (
statusCodesWhenCompleted ,
) } . includes ( $response . statusCode ) } } ` ;
} else if ( pagination . paginationCompleteWhen === 'responseIsEmpty' ) {
continueExpression =
'={{ Array.isArray($response.body) ? $response.body.length : !!$response.body }}' ;
} else {
// Other
if ( ! pagination . completeExpression . length || pagination . completeExpression [ 0 ] !== '=' ) {
throw new NodeOperationError ( this . getNode ( ) , 'Invalid or empty Complete Expression' ) ;
}
continueExpression = ` ={{ !( ${ pagination . completeExpression . trim ( ) . slice ( 3 , - 2 ) } ) }} ` ;
}
const paginationData : PaginationOptions = {
continue : continueExpression ,
request : { } ,
2024-01-04 07:11:16 -08:00
requestInterval : pagination.requestInterval ,
2023-11-01 06:24:43 -07:00
} ;
if ( pagination . paginationMode === 'updateAParameterInEachRequest' ) {
// Iterate over all parameters and add them to the request
paginationData . request = { } ;
2024-02-01 02:35:39 -08:00
const { parameters } = pagination . parameters ;
if ( parameters . length === 1 && parameters [ 0 ] . name === '' && parameters [ 0 ] . value === '' ) {
throw new NodeOperationError (
this . getNode ( ) ,
"At least one entry with 'Name' and 'Value' filled must be included in 'Parameters' to use 'Update a Parameter in Each Request' mode " ,
) ;
}
pagination . parameters . parameters . forEach ( ( parameter , index ) = > {
2023-11-01 06:24:43 -07:00
if ( ! paginationData . request [ parameter . type ] ) {
paginationData . request [ parameter . type ] = { } ;
}
2024-02-01 02:35:39 -08:00
const parameterName = parameter . name ;
if ( parameterName === '' ) {
throw new NodeOperationError (
this . getNode ( ) ,
` Parameter name must be set for parameter [ ${ index + 1 } ] in pagination settings ` ,
) ;
}
const parameterValue = parameter . value ;
if ( parameterValue === '' ) {
throw new NodeOperationError (
this . getNode ( ) ,
` Some value must be provided for parameter [ ${
index + 1
} ] in pagination settings , omitting it will result in an infinite loop ` ,
) ;
}
paginationData . request [ parameter . type ] ! [ parameterName ] = parameterValue ;
2023-11-01 06:24:43 -07:00
} ) ;
} else if ( pagination . paginationMode === 'responseContainsNextURL' ) {
paginationData . request . url = pagination . nextURL ;
}
if ( pagination . limitPagesFetched ) {
paginationData . maxRequests = pagination . maxRequests ;
}
if ( responseFormat === 'file' ) {
paginationData . binaryResult = true ;
}
2024-02-12 08:32:27 -08:00
const requestPromise = this . helpers . requestWithAuthenticationPaginated
. call (
this ,
requestOptions ,
itemIndex ,
paginationData ,
nodeCredentialType ? ? genericCredentialType ,
)
. catch ( ( error ) = > {
if ( error instanceof NodeOperationError && error . type === 'invalid_url' ) {
const urlParameterName =
pagination . paginationMode === 'responseContainsNextURL' ? 'Next URL' : 'URL' ;
throw new NodeOperationError ( this . getNode ( ) , error . message , {
description : ` Make sure the " ${ urlParameterName } " parameter evaluates to a valid URL. ` ,
} ) ;
}
throw error ;
} ) ;
2023-11-01 06:24:43 -07:00
requestPromises . push ( requestPromise ) ;
} else if ( authentication === 'genericCredentialType' || authentication === 'none' ) {
2022-09-29 14:28:02 -07:00
if ( oAuth1Api ) {
const requestOAuth1 = this . helpers . requestOAuth1 . call ( this , 'oAuth1Api' , requestOptions ) ;
requestOAuth1 . catch ( ( ) = > { } ) ;
requestPromises . push ( requestOAuth1 ) ;
} else if ( oAuth2Api ) {
const requestOAuth2 = this . helpers . requestOAuth2 . call ( this , 'oAuth2Api' , requestOptions , {
tokenType : 'Bearer' ,
} ) ;
requestOAuth2 . catch ( ( ) = > { } ) ;
requestPromises . push ( requestOAuth2 ) ;
} else {
// bearerAuth, queryAuth, headerAuth, digestAuth, none
const request = this . helpers . request ( requestOptions ) ;
request . catch ( ( ) = > { } ) ;
requestPromises . push ( request ) ;
}
} else if ( authentication === 'predefinedCredentialType' && nodeCredentialType ) {
2022-10-11 00:49:51 -07:00
const additionalOAuth2Options = getOAuth2AdditionalParameters ( nodeCredentialType ) ;
2022-09-29 14:28:02 -07:00
// service-specific cred: OAuth1, OAuth2, plain
const requestWithAuthentication = this . helpers . requestWithAuthentication . call (
this ,
nodeCredentialType ,
requestOptions ,
additionalOAuth2Options && { oauth2 : additionalOAuth2Options } ,
2024-01-31 04:12:18 -08:00
itemIndex ,
2022-09-29 14:28:02 -07:00
) ;
requestWithAuthentication . catch ( ( ) = > { } ) ;
requestPromises . push ( requestWithAuthentication ) ;
}
}
2024-06-05 00:25:39 -07:00
const sanitizedRequests : IDataObject [ ] = [ ] ;
const promisesResponses = await Promise . allSettled (
requestPromises . map (
async ( requestPromise , itemIndex ) = >
await requestPromise . finally ( async ( ) = > {
try {
// Secrets need to be read after the request because secrets could have changed
// For example: OAuth token refresh, preAuthentication
const { options , authKeys , credentialType } = requests [ itemIndex ] ;
let secrets : string [ ] = [ ] ;
if ( credentialType ) {
const properties = this . getCredentialsProperties ( credentialType ) ;
const credentials = await this . getCredentials ( credentialType , itemIndex ) ;
secrets = getSecrets ( properties , credentials ) ;
}
const sanitizedRequestOptions = sanitizeUiMessage ( options , authKeys , secrets ) ;
sanitizedRequests . push ( sanitizedRequestOptions ) ;
this . sendMessageToUI ( sanitizedRequestOptions ) ;
} catch ( e ) { }
} ) ,
) ,
) ;
2022-09-29 14:28:02 -07:00
2023-11-01 06:24:43 -07:00
let responseData : any ;
2022-09-29 14:28:02 -07:00
for ( let itemIndex = 0 ; itemIndex < items . length ; itemIndex ++ ) {
2023-11-01 06:24:43 -07:00
responseData = promisesResponses . shift ( ) ;
if ( responseData ! . status !== 'fulfilled' ) {
if ( responseData . reason . statusCode === 429 ) {
responseData . reason . message =
2023-09-28 07:00:45 -07:00
"Try spacing your requests out using the batching settings under 'Options'" ;
}
2022-12-02 12:54:28 -08:00
if ( ! this . continueOnFail ( ) ) {
2023-11-01 06:24:43 -07:00
if ( autoDetectResponseFormat && responseData . reason . error instanceof Buffer ) {
responseData . reason . error = Buffer . from ( responseData . reason . error as Buffer ) . toString ( ) ;
2022-09-29 14:28:02 -07:00
}
2024-06-27 03:22:07 -07:00
let error ;
if ( responseData ? . reason instanceof NodeApiError ) {
error = responseData . reason ;
set ( error , 'context.itemIndex' , itemIndex ) ;
} else {
const errorData = (
responseData . reason ? responseData.reason : responseData
) as JsonObject ;
error = new NodeApiError ( this . getNode ( ) , errorData , { itemIndex } ) ;
}
2024-06-05 00:25:39 -07:00
set ( error , 'context.request' , sanitizedRequests [ itemIndex ] ) ;
2024-06-27 03:22:07 -07:00
2024-03-07 08:08:01 -08:00
throw error ;
2022-09-29 14:28:02 -07:00
} else {
2023-11-01 06:24:43 -07:00
removeCircularRefs ( responseData . reason as JsonObject ) ;
2022-09-29 14:28:02 -07:00
// Return the actual reason as error
returnItems . push ( {
json : {
2023-11-01 06:24:43 -07:00
error : responseData.reason ,
2022-09-29 14:28:02 -07:00
} ,
pairedItem : {
item : itemIndex ,
} ,
} ) ;
continue ;
}
}
2023-11-01 06:24:43 -07:00
let responses : any [ ] ;
if ( Array . isArray ( responseData . value ) ) {
responses = responseData . value ;
} else {
responses = [ responseData . value ] ;
}
2022-09-29 14:28:02 -07:00
let responseFormat = this . getNodeParameter (
'options.response.response.responseFormat' ,
0 ,
'autodetect' ,
) as string ;
2022-12-02 12:54:28 -08:00
fullResponse = this . getNodeParameter (
2022-09-29 14:28:02 -07:00
'options.response.response.fullResponse' ,
0 ,
false ,
) as boolean ;
2023-11-01 06:24:43 -07:00
// eslint-disable-next-line prefer-const
for ( let [ index , response ] of Object . entries ( responses ) ) {
2024-01-10 09:05:19 -08:00
if ( response ? . request ? . constructor . name === 'ClientRequest' ) delete response . request ;
2023-11-01 06:24:43 -07:00
if ( this . getMode ( ) === 'manual' && index === '0' ) {
// For manual executions save the first response in the context
// so that we can use it in the frontend and so make it easier for
// the users to create the required pagination expressions
const nodeContext = this . getContext ( 'node' ) ;
if ( pagination && pagination . paginationMode !== 'off' ) {
nodeContext . response = responseData . value [ 0 ] ;
} else {
nodeContext . response = responseData . value ;
}
2022-09-29 14:28:02 -07:00
}
2024-02-08 08:12:00 -08:00
const responseContentType = response . headers [ 'content-type' ] ? ? '' ;
2023-11-01 06:24:43 -07:00
if ( autoDetectResponseFormat ) {
if ( responseContentType . includes ( 'application/json' ) ) {
responseFormat = 'json' ;
if ( ! response . __bodyResolved ) {
const neverError = this . getNodeParameter (
'options.response.response.neverError' ,
0 ,
false ,
) as boolean ;
2024-07-11 08:03:52 -07:00
const data = await this . helpers . binaryToString ( response . body as Buffer | Readable ) ;
2023-11-01 06:24:43 -07:00
response . body = jsonParse ( data , {
. . . ( neverError
? { fallbackValue : { } }
: { errorMessage : 'Invalid JSON in response body' } ) ,
} ) ;
}
} else if ( binaryContentTypes . some ( ( e ) = > responseContentType . includes ( e ) ) ) {
responseFormat = 'file' ;
} else {
responseFormat = 'text' ;
if ( ! response . __bodyResolved ) {
2024-07-11 08:03:52 -07:00
const data = await this . helpers . binaryToString ( response . body as Buffer | Readable ) ;
2023-11-01 06:24:43 -07:00
response . body = ! data ? undefined : data ;
2022-09-29 14:28:02 -07:00
}
}
2023-11-01 06:24:43 -07:00
}
2022-09-29 14:28:02 -07:00
2023-11-01 06:24:43 -07:00
if ( autoDetectResponseFormat && ! fullResponse ) {
delete response . headers ;
delete response . statusCode ;
delete response . statusMessage ;
}
if ( ! fullResponse ) {
response = response . body ;
2022-09-29 14:28:02 -07:00
}
2023-11-01 06:24:43 -07:00
if ( responseFormat === 'file' ) {
const outputPropertyName = this . getNodeParameter (
'options.response.response.outputPropertyName' ,
0 ,
'data' ,
) as string ;
const newItem : INodeExecutionData = {
json : { } ,
binary : { } ,
2022-09-29 14:28:02 -07:00
pairedItem : {
item : itemIndex ,
} ,
2023-11-01 06:24:43 -07:00
} ;
if ( items [ itemIndex ] . binary !== undefined ) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object . assign ( newItem . binary as IBinaryKeyData , items [ itemIndex ] . binary ) ;
2022-09-29 14:28:02 -07:00
}
2023-11-01 06:24:43 -07:00
let binaryData : Buffer | Readable ;
if ( fullResponse ) {
const returnItem : IDataObject = { } ;
for ( const property of fullResponseProperties ) {
if ( property === 'body' ) {
continue ;
}
returnItem [ property ] = response [ property ] ;
2022-09-29 14:28:02 -07:00
}
2023-11-01 06:24:43 -07:00
newItem . json = returnItem ;
binaryData = response ? . body ;
} else {
newItem . json = items [ itemIndex ] . json ;
binaryData = response ;
2022-09-29 14:28:02 -07:00
}
2024-02-08 08:12:00 -08:00
const preparedBinaryData = await this . helpers . prepareBinaryData (
binaryData ,
undefined ,
responseContentType || undefined ,
) ;
if (
! preparedBinaryData . fileName &&
preparedBinaryData . fileExtension &&
typeof requestOptions . uri === 'string' &&
requestOptions . uri . endsWith ( preparedBinaryData . fileExtension )
) {
preparedBinaryData . fileName = requestOptions . uri . split ( '/' ) . pop ( ) ;
}
newItem . binary ! [ outputPropertyName ] = preparedBinaryData ;
2022-09-29 14:28:02 -07:00
2023-11-01 06:24:43 -07:00
returnItems . push ( newItem ) ;
} else if ( responseFormat === 'text' ) {
const outputPropertyName = this . getNodeParameter (
'options.response.response.outputPropertyName' ,
0 ,
'data' ,
) as string ;
if ( fullResponse ) {
const returnItem : IDataObject = { } ;
for ( const property of fullResponseProperties ) {
if ( property === 'body' ) {
returnItem [ outputPropertyName ] = toText ( response [ property ] ) ;
continue ;
}
returnItem [ property ] = response [ property ] ;
2022-09-29 14:28:02 -07:00
}
2023-11-01 06:24:43 -07:00
returnItems . push ( {
json : returnItem ,
pairedItem : {
item : itemIndex ,
} ,
} ) ;
} else {
returnItems . push ( {
json : {
[ outputPropertyName ] : toText ( response ) ,
} ,
pairedItem : {
item : itemIndex ,
} ,
} ) ;
2022-09-29 14:28:02 -07:00
}
2023-11-01 06:24:43 -07:00
} else {
// responseFormat: 'json'
if ( fullResponse ) {
const returnItem : IDataObject = { } ;
for ( const property of fullResponseProperties ) {
returnItem [ property ] = response [ property ] ;
}
if ( responseFormat === 'json' && typeof returnItem . body === 'string' ) {
try {
returnItem . body = JSON . parse ( returnItem . body ) ;
} catch ( error ) {
throw new NodeOperationError (
this . getNode ( ) ,
'Response body is not valid JSON. Change "Response Format" to "Text"' ,
{ itemIndex } ,
) ;
}
}
2022-09-29 14:28:02 -07:00
returnItems . push ( {
2023-11-01 06:24:43 -07:00
json : returnItem ,
2022-09-29 14:28:02 -07:00
pairedItem : {
item : itemIndex ,
} ,
} ) ;
2023-11-01 06:24:43 -07:00
} else {
if ( responseFormat === 'json' && typeof response === 'string' ) {
try {
if ( typeof response !== 'object' ) {
response = JSON . parse ( response ) ;
}
} catch ( error ) {
throw new NodeOperationError (
this . getNode ( ) ,
'Response body is not valid JSON. Change "Response Format" to "Text"' ,
{ itemIndex } ,
) ;
}
}
if ( Array . isArray ( response ) ) {
// eslint-disable-next-line @typescript-eslint/no-loop-func
response . forEach ( ( item ) = >
returnItems . push ( {
json : item ,
pairedItem : {
item : itemIndex ,
} ,
} ) ,
) ;
} else {
returnItems . push ( {
json : response ,
pairedItem : {
item : itemIndex ,
} ,
} ) ;
}
2022-09-29 14:28:02 -07:00
}
}
}
}
returnItems = returnItems . map ( replaceNullValues ) ;
2023-09-05 03:59:02 -07:00
return [ returnItems ] ;
2022-09-29 14:28:02 -07:00
}
}