import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import axios, { AxiosRequestConfig, Method, ResponseType } from 'axios' import FormData from 'form-data' import * as querystring from 'querystring' import { getCredentialData, getCredentialParam } from '../../../src/utils' import { checkDenyList } from '../../../src/httpSecurity' class HTTP_Agentflow implements INode { label: string name: string version: number description: string type: string icon: string category: string color: string baseClasses: string[] documentation?: string credential: INodeParams inputs: INodeParams[] private sanitizeJsonString(jsonString: string): string { // Remove common problematic escape sequences that are not valid JSON let sanitized = jsonString // Remove escaped square brackets (not valid JSON) .replace(/\\(\[|\])/g, '$1') // Fix unquoted string values in JSON (simple case) .replace(/:\s*([a-zA-Z][a-zA-Z0-9]*)\s*([,}])/g, ': "$1"$2') // Fix trailing commas .replace(/,(\s*[}\]])/g, '$1') return sanitized } private parseJsonBody(body: string): any { try { // First try to parse as-is return JSON.parse(body) } catch (error) { try { // If that fails, try to sanitize and parse const sanitized = this.sanitizeJsonString(body) return JSON.parse(sanitized) } catch (sanitizeError) { // If sanitization also fails, throw the original error with helpful message throw new Error( `Invalid JSON format in body. Original error: ${error.message}. Please ensure your JSON is properly formatted with quoted strings and valid escape sequences.` ) } } } constructor() { this.label = 'HTTP' this.name = 'httpAgentflow' this.version = 1.1 this.type = 'HTTP' this.category = 'Agent Flows' this.description = 'Send a HTTP request' this.baseClasses = [this.type] this.color = '#FF7F7F' this.credential = { label: 'HTTP Credential', name: 'credential', type: 'credential', credentialNames: ['httpBasicAuth', 'httpBearerToken', 'httpApiKey'], optional: true } this.inputs = [ { label: 'Method', name: 'method', type: 'options', options: [ { label: 'GET', name: 'GET' }, { label: 'POST', name: 'POST' }, { label: 'PUT', name: 'PUT' }, { label: 'DELETE', name: 'DELETE' }, { label: 'PATCH', name: 'PATCH' } ], default: 'GET' }, { label: 'URL', name: 'url', type: 'string' }, { label: 'Headers', name: 'headers', type: 'array', acceptVariable: true, array: [ { label: 'Key', name: 'key', type: 'string', default: '' }, { label: 'Value', name: 'value', type: 'string', default: '', acceptVariable: true } ], optional: true }, { label: 'Query Params', name: 'queryParams', type: 'array', acceptVariable: true, array: [ { label: 'Key', name: 'key', type: 'string', default: '' }, { label: 'Value', name: 'value', type: 'string', default: '', acceptVariable: true } ], optional: true }, { label: 'Body Type', name: 'bodyType', type: 'options', options: [ { label: 'JSON', name: 'json' }, { label: 'Raw', name: 'raw' }, { label: 'Form Data', name: 'formData' }, { label: 'x-www-form-urlencoded', name: 'xWwwFormUrlencoded' } ], optional: true }, { label: 'Body', name: 'body', type: 'string', acceptVariable: true, rows: 4, show: { bodyType: ['raw', 'json'] }, optional: true }, { label: 'Body', name: 'body', type: 'array', acceptVariable: true, show: { bodyType: ['xWwwFormUrlencoded', 'formData'] }, array: [ { label: 'Key', name: 'key', type: 'string', default: '' }, { label: 'Value', name: 'value', type: 'string', default: '', acceptVariable: true } ], optional: true }, { label: 'Response Type', name: 'responseType', type: 'options', options: [ { label: 'JSON', name: 'json' }, { label: 'Text', name: 'text' }, { label: 'Array Buffer', name: 'arraybuffer' }, { label: 'Raw (Base64)', name: 'base64' } ], optional: true } ] } async run(nodeData: INodeData, _: string, options: ICommonObject): Promise { const method = nodeData.inputs?.method as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' const url = nodeData.inputs?.url as string const headers = nodeData.inputs?.headers as ICommonObject const queryParams = nodeData.inputs?.queryParams as ICommonObject const bodyType = nodeData.inputs?.bodyType as 'json' | 'raw' | 'formData' | 'xWwwFormUrlencoded' const body = nodeData.inputs?.body as ICommonObject | string | ICommonObject[] const responseType = nodeData.inputs?.responseType as 'json' | 'text' | 'arraybuffer' | 'base64' const state = options.agentflowRuntime?.state as ICommonObject try { // Prepare headers const requestHeaders: Record = {} // Add headers from inputs if (headers && Array.isArray(headers)) { for (const header of headers) { if (header.key && header.value) { requestHeaders[header.key] = header.value } } } // Add credentials if provided const credentialData = await getCredentialData(nodeData.credential ?? '', options) if (credentialData && Object.keys(credentialData).length !== 0) { const basicAuthUsername = getCredentialParam('basicAuthUsername', credentialData, nodeData) const basicAuthPassword = getCredentialParam('basicAuthPassword', credentialData, nodeData) const bearerToken = getCredentialParam('token', credentialData, nodeData) const apiKeyName = getCredentialParam('key', credentialData, nodeData) const apiKeyValue = getCredentialParam('value', credentialData, nodeData) // Determine which type of auth to use based on available credentials if (basicAuthUsername || basicAuthPassword) { // Basic Auth const auth = Buffer.from(`${basicAuthUsername}:${basicAuthPassword}`).toString('base64') requestHeaders['Authorization'] = `Basic ${auth}` } else if (bearerToken) { // Bearer Token requestHeaders['Authorization'] = `Bearer ${bearerToken}` } else if (apiKeyName && apiKeyValue) { // API Key in header requestHeaders[apiKeyName] = apiKeyValue } } // Prepare query parameters let queryString = '' if (queryParams && Array.isArray(queryParams)) { const params = new URLSearchParams() for (const param of queryParams) { if (param.key && param.value) { params.append(param.key, param.value) } } queryString = params.toString() } // Build final URL with query parameters const finalUrl = queryString ? `${url}${url.includes('?') ? '&' : '?'}${queryString}` : url await checkDenyList(finalUrl) // Prepare request config const requestConfig: AxiosRequestConfig = { method: method as Method, url: finalUrl, headers: requestHeaders, responseType: (responseType || 'json') as ResponseType } // Handle request body based on body type if (method !== 'GET' && body) { switch (bodyType) { case 'json': { requestConfig.data = typeof body === 'string' ? this.parseJsonBody(body) : body requestHeaders['Content-Type'] = 'application/json' break } case 'raw': requestConfig.data = body break case 'formData': { const formData = new FormData() if (Array.isArray(body) && body.length > 0) { for (const item of body) { formData.append(item.key, item.value) } } requestConfig.data = formData break } case 'xWwwFormUrlencoded': requestConfig.data = querystring.stringify(typeof body === 'string' ? this.parseJsonBody(body) : body) requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded' break } } // Make the HTTP request const response = await axios(requestConfig) // Process response based on response type let responseData if (responseType === 'base64' && response.data) { responseData = Buffer.from(response.data, 'binary').toString('base64') } else { responseData = response.data } const returnOutput = { id: nodeData.id, name: this.name, input: { http: { method, url, headers, queryParams, bodyType, body, responseType } }, output: { http: { data: responseData, status: response.status, statusText: response.statusText, headers: response.headers } }, state } return returnOutput } catch (error) { console.error('HTTP Request Error:', error) const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || 'An error occurred during the HTTP request' // Format error response const errorResponse: any = { id: nodeData.id, name: this.name, input: { http: { method, url, headers, queryParams, bodyType, body, responseType } }, error: { name: error.name || 'Error', message: errorMessage }, state } // Add more error details if available if (error.response) { errorResponse.error.status = error.response.status errorResponse.error.statusText = error.response.statusText errorResponse.error.data = error.response.data errorResponse.error.headers = error.response.headers } throw new Error(errorMessage) } } } module.exports = { nodeClass: HTTP_Agentflow }