diff --git a/packages/nodes-base/nodes/HttpRequest/V3/Description.ts b/packages/nodes-base/nodes/HttpRequest/V3/Description.ts
new file mode 100644
index 0000000000..827c8f32ea
--- /dev/null
+++ b/packages/nodes-base/nodes/HttpRequest/V3/Description.ts
@@ -0,0 +1,1177 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const mainProperties: INodeProperties[] = [
+ {
+ 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'],
+ },
+ },
+ },
+ {
+ 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'],
+ },
+ },
+ },
+ {
+ displayName: 'Generic Auth Type',
+ name: 'genericAuthType',
+ type: 'credentialsSelect',
+ required: true,
+ default: '',
+ credentialTypes: ['has:genericAuth'],
+ displayOptions: {
+ show: {
+ authentication: ['genericCredentialType'],
+ },
+ },
+ },
+ {
+ 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],
+ },
+ },
+ },
+ {
+ displayName: 'Send Query Parameters',
+ name: 'sendQuery',
+ type: 'boolean',
+ default: false,
+ noDataExpression: true,
+ description: 'Whether the request has query params or not',
+ },
+ {
+ 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',
+ },
+ {
+ displayName: 'Query Parameters',
+ name: 'queryParameters',
+ type: 'fixedCollection',
+ displayOptions: {
+ show: {
+ sendQuery: [true],
+ specifyQuery: ['keypair'],
+ },
+ },
+ 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: '',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'JSON',
+ name: 'jsonQuery',
+ type: 'json',
+ displayOptions: {
+ show: {
+ sendQuery: [true],
+ specifyQuery: ['json'],
+ },
+ },
+ default: '',
+ },
+ {
+ displayName: 'Send Headers',
+ name: 'sendHeaders',
+ type: 'boolean',
+ default: false,
+ noDataExpression: true,
+ description: 'Whether the request has headers or not',
+ },
+ {
+ 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',
+ },
+ {
+ displayName: 'Header Parameters',
+ name: 'headerParameters',
+ type: 'fixedCollection',
+ displayOptions: {
+ show: {
+ sendHeaders: [true],
+ specifyHeaders: ['keypair'],
+ },
+ },
+ 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: '',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'JSON',
+ name: 'jsonHeaders',
+ type: 'json',
+ displayOptions: {
+ show: {
+ sendHeaders: [true],
+ specifyHeaders: ['json'],
+ },
+ },
+ default: '',
+ },
+ {
+ 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
+ name: 'n8n Binary File',
+ 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',
+ // eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-json
+ description:
+ 'The body can be specified using explicit fields (keypair) or using a JavaScript object (json)',
+ },
+ {
+ 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 expression.',
+ },
+ {
+ 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
+ name: 'n8n Binary File',
+ value: 'formBinaryData',
+ },
+ {
+ name: 'Form Data',
+ value: 'formData',
+ },
+ ],
+ default: 'formData',
+ },
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description:
+ 'ID of the field to set. Choose from the list, or specify an ID using an expression.',
+ },
+ {
+ 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:
+ 'ID of the field to set. Choose from the list, or specify an ID using an expression.',
+ },
+ {
+ 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',
+ placeholder: 'Add option',
+ 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',
+ },
+ {
+ displayName: 'Lowercase Headers',
+ name: 'lowercaseHeaders',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to lowercase header names',
+ },
+ {
+ 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],
+ },
+ },
+ },
+ {
+ 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: true,
+ 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: {
+ hide: {
+ '@version': [1, 2, 3],
+ },
+ },
+ },
+ {
+ 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:
+ 'Whether to return the full response (headers and response status code) data instead of only the body',
+ },
+ {
+ 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',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ 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:
+ 'Use the $response variables to access the data of the previous response. Refer to the docs for more info about pagination/',
+ name: 'webhookNotice',
+ displayOptions: {
+ hide: {
+ paginationMode: ['off'],
+ },
+ },
+ type: 'notice',
+ default: '',
+ },
+ {
+ displayName: 'Next URL',
+ name: 'nextURL',
+ type: 'string',
+ displayOptions: {
+ show: {
+ paginationMode: ['responseContainsNextURL'],
+ },
+ },
+ default: '',
+ description:
+ 'Should evaluate to the URL of the next page. More info.',
+ },
+ {
+ 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: '',
+ placeholder: 'e.g page',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ hint: 'Use expression mode and $response to access response data',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ 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:
+ 'Should evaluate to true when pagination is complete. More info.',
+ },
+ {
+ 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',
+ },
+ {
+ // 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,
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ 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',
+ },
+ ],
+ },
+ {
+ displayName:
+ "You can view the raw requests this node makes in your browser's developer console",
+ name: 'infoMessage',
+ type: 'notice',
+ default: '',
+ },
+];
diff --git a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts
index 9ff67f9481..682b83cd1d 100644
--- a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts
+++ b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts
@@ -1,5 +1,4 @@
-import type { Readable } from 'stream';
-
+import set from 'lodash/set';
import type {
IBinaryKeyData,
IDataObject,
@@ -14,7 +13,6 @@ import type {
IRequestOptions,
IHttpRequestMethods,
} from 'n8n-workflow';
-
import {
BINARY_ENCODING,
NodeApiError,
@@ -25,8 +23,10 @@ import {
removeCircularRefs,
sleep,
} from 'n8n-workflow';
+import type { Readable } from 'stream';
+
+import { keysToLowercase } from '@utils/utilities';
-import set from 'lodash/set';
import type { BodyParameter, IAuthDataSanitizeKeys } from '../GenericFunctions';
import {
binaryContentTypes,
@@ -38,8 +38,7 @@ import {
sanitizeUiMessage,
setAgentOptions,
} from '../GenericFunctions';
-import { keysToLowercase } from '@utils/utilities';
-import { type HttpSslAuthCredentials } from '../interfaces';
+import { mainProperties } from './Description';
function toText(data: T) {
if (typeof data === 'object' && data !== null) {
@@ -72,1182 +71,7 @@ export class HttpRequestV3 implements INodeType {
},
},
],
- 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'],
- },
- },
- },
- {
- 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'],
- },
- },
- },
- {
- displayName: 'Generic Auth Type',
- name: 'genericAuthType',
- type: 'credentialsSelect',
- required: true,
- default: '',
- credentialTypes: ['has:genericAuth'],
- displayOptions: {
- show: {
- authentication: ['genericCredentialType'],
- },
- },
- },
- {
- 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],
- },
- },
- },
- {
- displayName: 'Send Query Parameters',
- name: 'sendQuery',
- type: 'boolean',
- default: false,
- noDataExpression: true,
- description: 'Whether the request has query params or not',
- },
- {
- 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',
- },
- {
- displayName: 'Query Parameters',
- name: 'queryParameters',
- type: 'fixedCollection',
- displayOptions: {
- show: {
- sendQuery: [true],
- specifyQuery: ['keypair'],
- },
- },
- 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: '',
- },
- ],
- },
- ],
- },
- {
- displayName: 'JSON',
- name: 'jsonQuery',
- type: 'json',
- displayOptions: {
- show: {
- sendQuery: [true],
- specifyQuery: ['json'],
- },
- },
- default: '',
- },
- {
- displayName: 'Send Headers',
- name: 'sendHeaders',
- type: 'boolean',
- default: false,
- noDataExpression: true,
- description: 'Whether the request has headers or not',
- },
- {
- 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',
- },
- {
- displayName: 'Header Parameters',
- name: 'headerParameters',
- type: 'fixedCollection',
- displayOptions: {
- show: {
- sendHeaders: [true],
- specifyHeaders: ['keypair'],
- },
- },
- 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: '',
- },
- ],
- },
- ],
- },
- {
- displayName: 'JSON',
- name: 'jsonHeaders',
- type: 'json',
- displayOptions: {
- show: {
- sendHeaders: [true],
- specifyHeaders: ['json'],
- },
- },
- default: '',
- },
- {
- 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
- name: 'n8n Binary File',
- 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',
- // eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-json
- description:
- 'The body can be specified using explicit fields (keypair) or using a JavaScript object (json)',
- },
- {
- 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 expression.',
- },
- {
- 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
- name: 'n8n Binary File',
- value: 'formBinaryData',
- },
- {
- name: 'Form Data',
- value: 'formData',
- },
- ],
- default: 'formData',
- },
- {
- displayName: 'Name',
- name: 'name',
- type: 'string',
- default: '',
- description:
- 'ID of the field to set. Choose from the list, or specify an ID using an expression.',
- },
- {
- 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:
- 'ID of the field to set. Choose from the list, or specify an ID using an expression.',
- },
- {
- 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',
- placeholder: 'Add option',
- 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',
- },
- {
- displayName: 'Lowercase Headers',
- name: 'lowercaseHeaders',
- type: 'boolean',
- default: true,
- description: 'Whether to lowercase header names',
- },
- {
- 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],
- },
- },
- },
- {
- 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: true,
- 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: {
- hide: {
- '@version': [1, 2, 3],
- },
- },
- },
- {
- 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:
- 'Whether to return the full response (headers and response status code) data instead of only the body',
- },
- {
- 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',
- },
- ],
- },
- ],
- },
- {
- 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:
- 'Use the $response variables to access the data of the previous response. Refer to the docs for more info about pagination/',
- name: 'webhookNotice',
- displayOptions: {
- hide: {
- paginationMode: ['off'],
- },
- },
- type: 'notice',
- default: '',
- },
- {
- displayName: 'Next URL',
- name: 'nextURL',
- type: 'string',
- displayOptions: {
- show: {
- paginationMode: ['responseContainsNextURL'],
- },
- },
- default: '',
- description:
- 'Should evaluate to the URL of the next page. More info.',
- },
- {
- 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: '',
- placeholder: 'e.g page',
- },
- {
- displayName: 'Value',
- name: 'value',
- type: 'string',
- default: '',
- hint: 'Use expression mode and $response to access response data',
- },
- ],
- },
- ],
- },
- {
- 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:
- 'Should evaluate to true when pagination is complete. More info.',
- },
- {
- 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',
- },
- {
- // 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,
- },
- },
- ],
- },
- ],
- },
- {
- 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',
- },
- ],
- },
- {
- displayName:
- "You can view the raw requests this node makes in your browser's developer console",
- name: 'infoMessage',
- type: 'notice',
- default: '',
- },
- ],
+ properties: mainProperties,
};
}
@@ -1345,10 +169,7 @@ export class HttpRequestV3 implements INodeType {
);
if (provideSslCertificates) {
- sslCertificates = (await this.getCredentials(
- 'httpSslAuth',
- itemIndex,
- )) as HttpSslAuthCredentials;
+ sslCertificates = await this.getCredentials('httpSslAuth', itemIndex);
}
const requestMethod = this.getNodeParameter('method', itemIndex) as IHttpRequestMethods;
diff --git a/packages/nodes-base/nodes/HttpRequest/test/node/HttpRequestV3.test.ts b/packages/nodes-base/nodes/HttpRequest/test/node/HttpRequestV3.test.ts
new file mode 100644
index 0000000000..d245c77e7f
--- /dev/null
+++ b/packages/nodes-base/nodes/HttpRequest/test/node/HttpRequestV3.test.ts
@@ -0,0 +1,229 @@
+/* eslint-disable n8n-nodes-base/node-filename-against-convention */
+import type { IExecuteFunctions, INodeTypeBaseDescription } from 'n8n-workflow';
+
+import { HttpRequestV3 } from '../../V3/HttpRequestV3.node';
+
+describe('HttpRequestV3', () => {
+ let node: HttpRequestV3;
+ let executeFunctions: IExecuteFunctions;
+
+ const baseUrl = 'http://example.com';
+ const options = {
+ redirect: '',
+ batching: { batch: { batchSize: 1, batchInterval: 1 } },
+ proxy: '',
+ timeout: '',
+ allowUnauthoridCerts: '',
+ queryParameterArrays: '',
+ response: '',
+ lowercaseHeaders: '',
+ };
+
+ beforeEach(() => {
+ const baseDescription: INodeTypeBaseDescription = {
+ displayName: 'HTTP Request',
+ name: 'httpRequest',
+ description: 'Makes an HTTP request and returns the response data',
+ group: [],
+ };
+ node = new HttpRequestV3(baseDescription);
+ executeFunctions = {
+ getInputData: jest.fn(),
+ getNodeParameter: jest.fn(),
+ getNode: jest.fn(() => {
+ return {
+ type: 'n8n-nodes-base.httpRequest',
+ typeVersion: 3,
+ };
+ }),
+ getCredentials: jest.fn(),
+ helpers: {
+ request: jest.fn(),
+ requestOAuth1: jest.fn(
+ async () =>
+ await Promise.resolve({
+ statusCode: 200,
+ headers: { 'content-type': 'application/json' },
+ body: Buffer.from(JSON.stringify({ success: true })),
+ }),
+ ),
+ requestOAuth2: jest.fn(
+ async () =>
+ await Promise.resolve({
+ statusCode: 200,
+ headers: { 'content-type': 'application/json' },
+ body: Buffer.from(JSON.stringify({ success: true })),
+ }),
+ ),
+ requestWithAuthentication: jest.fn(),
+ requestWithAuthenticationPaginated: jest.fn(),
+ assertBinaryData: jest.fn(),
+ getBinaryStream: jest.fn(),
+ getBinaryMetadata: jest.fn(),
+ binaryToString: jest.fn((buffer: Buffer) => {
+ return buffer.toString();
+ }),
+ prepareBinaryData: jest.fn(),
+ },
+ getContext: jest.fn(),
+ sendMessageToUI: jest.fn(),
+ continueOnFail: jest.fn(),
+ getMode: jest.fn(),
+ } as unknown as IExecuteFunctions;
+ });
+
+ it('should make a GET request', async () => {
+ (executeFunctions.getInputData as jest.Mock).mockReturnValue([{ json: {} }]);
+ (executeFunctions.getNodeParameter as jest.Mock).mockImplementation((paramName: string) => {
+ switch (paramName) {
+ case 'method':
+ return 'GET';
+ case 'url':
+ return baseUrl;
+ case 'authentication':
+ return 'none';
+ case 'options':
+ return options;
+ default:
+ return undefined;
+ }
+ });
+ const response = {
+ headers: { 'content-type': 'application/json' },
+ body: Buffer.from(JSON.stringify({ success: true })),
+ };
+
+ (executeFunctions.helpers.request as jest.Mock).mockResolvedValue(response);
+
+ const result = await node.execute.call(executeFunctions);
+
+ expect(result).toEqual([[{ json: { success: true }, pairedItem: { item: 0 } }]]);
+ });
+
+ it('should handle authentication', async () => {
+ (executeFunctions.getInputData as jest.Mock).mockReturnValue([{ json: {} }]);
+ (executeFunctions.getNodeParameter as jest.Mock).mockImplementation((paramName: string) => {
+ switch (paramName) {
+ case 'method':
+ return 'GET';
+ case 'url':
+ return baseUrl;
+ case 'authentication':
+ return 'genericCredentialType';
+ case 'genericAuthType':
+ return 'httpBasicAuth';
+ case 'options':
+ return options;
+ default:
+ return undefined;
+ }
+ });
+ (executeFunctions.getCredentials as jest.Mock).mockResolvedValue({
+ user: 'username',
+ password: 'password',
+ });
+ const response = {
+ headers: { 'content-type': 'application/json' },
+ body: Buffer.from(JSON.stringify({ success: true })),
+ };
+ (executeFunctions.helpers.request as jest.Mock).mockResolvedValue(response);
+
+ const result = await node.execute.call(executeFunctions);
+
+ expect(result).toEqual([[{ json: { success: true }, pairedItem: { item: 0 } }]]);
+ expect(executeFunctions.helpers.request).toHaveBeenCalledWith(
+ expect.objectContaining({
+ auth: {
+ user: 'username',
+ pass: 'password',
+ },
+ }),
+ );
+ });
+
+ describe('Authentication Handling', () => {
+ const authenticationTypes = [
+ {
+ genericCredentialType: 'httpBasicAuth',
+ credentials: { user: 'username', password: 'password' },
+ authField: 'auth',
+ authValue: { user: 'username', pass: 'password' },
+ },
+ {
+ genericCredentialType: 'httpDigestAuth',
+ credentials: { user: 'username', password: 'password' },
+ authField: 'auth',
+ authValue: { user: 'username', pass: 'password', sendImmediately: false },
+ },
+ {
+ genericCredentialType: 'httpHeaderAuth',
+ credentials: { name: 'Authorization', value: 'Bearer token' },
+ authField: 'headers',
+ authValue: { Authorization: 'Bearer token' },
+ },
+ {
+ genericCredentialType: 'httpQueryAuth',
+ credentials: { name: 'Token', value: 'secretToken' },
+ authField: 'qs',
+ authValue: { Token: 'secretToken' },
+ },
+ {
+ genericCredentialType: 'oAuth1Api',
+ credentials: { oauth_token: 'token', oauth_token_secret: 'secret' },
+ authField: 'oauth',
+ authValue: { oauth_token: 'token', oauth_token_secret: 'secret' },
+ },
+ {
+ genericCredentialType: 'oAuth2Api',
+ credentials: { access_token: 'accessToken' },
+ authField: 'auth',
+ authValue: { bearer: 'accessToken' },
+ },
+ ];
+
+ it.each(authenticationTypes)(
+ 'should handle %s authentication',
+ async ({ genericCredentialType, credentials, authField, authValue }) => {
+ (executeFunctions.getInputData as jest.Mock).mockReturnValue([{ json: {} }]);
+ (executeFunctions.getNodeParameter as jest.Mock).mockImplementation((paramName: string) => {
+ switch (paramName) {
+ case 'method':
+ return 'GET';
+ case 'url':
+ return baseUrl;
+ case 'authentication':
+ return 'genericCredentialType';
+ case 'genericAuthType':
+ return genericCredentialType;
+ case 'options':
+ return options;
+ default:
+ return undefined;
+ }
+ });
+
+ (executeFunctions.getCredentials as jest.Mock).mockResolvedValue(credentials);
+ const response = {
+ headers: { 'content-type': 'application/json' },
+ body: Buffer.from(JSON.stringify({ success: true })),
+ };
+ (executeFunctions.helpers.request as jest.Mock).mockResolvedValue(response);
+
+ const result = await node.execute.call(executeFunctions);
+
+ expect(result).toEqual([[{ json: { success: true }, pairedItem: { item: 0 } }]]);
+ if (genericCredentialType === 'oAuth1Api') {
+ expect(executeFunctions.helpers.requestOAuth1).toHaveBeenCalled();
+ } else if (genericCredentialType === 'oAuth2Api') {
+ expect(executeFunctions.helpers.requestOAuth2).toHaveBeenCalled();
+ } else {
+ expect(executeFunctions.helpers.request).toHaveBeenCalledWith(
+ expect.objectContaining({
+ [authField]: expect.objectContaining(authValue),
+ }),
+ );
+ }
+ },
+ );
+ });
+});
diff --git a/packages/nodes-base/nodes/HttpRequest/test/utils/utils.test.ts b/packages/nodes-base/nodes/HttpRequest/test/utils/utils.test.ts
index 04c1107893..ae021bf574 100644
--- a/packages/nodes-base/nodes/HttpRequest/test/utils/utils.test.ts
+++ b/packages/nodes-base/nodes/HttpRequest/test/utils/utils.test.ts
@@ -1,9 +1,17 @@
-import type { IRequestOptions } from 'n8n-workflow';
+import type {
+ ICredentialDataDecryptedObject,
+ INodeExecutionData,
+ INodeProperties,
+ IRequestOptions,
+} from 'n8n-workflow';
+
import {
REDACTED,
prepareRequestBody,
sanitizeUiMessage,
setAgentOptions,
+ replaceNullValues,
+ getSecrets,
} from '../../GenericFunctions';
import type { BodyParameter, BodyParametersReducer } from '../../GenericFunctions';
@@ -210,4 +218,113 @@ describe('HTTP Node Utils', () => {
expect(sanitizedRequest.headers).toBeUndefined();
});
});
+
+ describe('replaceNullValues', () => {
+ it('should replace null json with an empty object', () => {
+ const item: INodeExecutionData = {
+ json: {},
+ };
+ const result = replaceNullValues(item);
+ expect(result.json).toEqual({});
+ });
+
+ it('should not modify json if it is already an object', () => {
+ const jsonObject = { key: 'value' };
+ const item: INodeExecutionData = { json: jsonObject };
+ const result = replaceNullValues(item);
+ expect(result.json).toBe(jsonObject);
+ });
+ });
+
+ describe('getSecrets', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should return secrets for sensitive properties', () => {
+ const properties: INodeProperties[] = [
+ {
+ displayName: 'Api Key',
+ name: 'apiKey',
+ typeOptions: { password: true },
+ type: 'string',
+ default: undefined,
+ },
+ {
+ displayName: 'Username',
+ name: 'username',
+ type: 'string',
+ default: undefined,
+ },
+ ];
+ const credentials: ICredentialDataDecryptedObject = {
+ apiKey: 'sensitive-api-key',
+ username: 'user123',
+ };
+
+ const secrets = getSecrets(properties, credentials);
+ expect(secrets).toEqual(['sensitive-api-key']);
+ });
+
+ it('should not return non-sensitive properties', () => {
+ const properties: INodeProperties[] = [
+ {
+ displayName: 'Username',
+ name: 'username',
+ type: 'string',
+ default: undefined,
+ },
+ ];
+ const credentials: ICredentialDataDecryptedObject = {
+ username: 'user123',
+ };
+
+ const secrets = getSecrets(properties, credentials);
+ expect(secrets).toEqual([]);
+ });
+
+ it('should not include non-string values in sensitive properties', () => {
+ const properties: INodeProperties[] = [
+ {
+ displayName: 'ApiKey',
+ name: 'apiKey',
+ typeOptions: { password: true },
+ type: 'string',
+ default: undefined,
+ },
+ ];
+ const credentials: ICredentialDataDecryptedObject = {
+ apiKey: 12345,
+ };
+
+ const secrets = getSecrets(properties, credentials);
+ expect(secrets).toEqual([]);
+ });
+
+ it('should return an empty array if properties and credentials are empty', () => {
+ const properties: INodeProperties[] = [];
+ const credentials: ICredentialDataDecryptedObject = {};
+
+ const secrets = getSecrets(properties, credentials);
+ expect(secrets).toEqual([]);
+ });
+
+ it('should not include null or undefined values in sensitive properties', () => {
+ const properties: INodeProperties[] = [
+ {
+ displayName: 'ApiKey',
+ name: 'apiKey',
+ typeOptions: { password: true },
+ type: 'string',
+ default: undefined,
+ },
+ ];
+ const credentials: ICredentialDataDecryptedObject = {
+ apiKey: {},
+ };
+
+ const secrets = getSecrets(properties, credentials);
+ expect(secrets).toEqual([]);
+ });
+ });
});