- Introduced a new component, ApiNodeConfiguration, for configuring API call nodes within the process builder. - Enhanced ProcessBuilderComponents to include the new API Call node type with default properties. - Implemented ApiCallNode in ProcessFlowNodes for rendering API call nodes with relevant details. - Added a backend endpoint for testing API node configurations, allowing users to validate API calls without executing the entire process. - Updated VariableManager to default to global scope for new variables, ensuring consistency in variable management. - Improved the overall process builder experience by integrating API call functionality and enhancing variable handling.
173 lines
4.9 KiB
JavaScript
173 lines
4.9 KiB
JavaScript
/**
|
|
* API Node Service
|
|
*
|
|
* This service handles the execution of API Call nodes in the process flow.
|
|
* It supports dynamic variable substitution in URLs, headers, and request bodies.
|
|
*/
|
|
|
|
// Helper function to substitute process variables in a string
|
|
// Example: "Hello {name}" with {name: "World"} becomes "Hello World"
|
|
function substituteVariables(text, variables) {
|
|
if (!text || typeof text !== 'string') return text;
|
|
|
|
return text.replace(/{([^{}]+)}/g, (match, variableName) => {
|
|
const variable = variables[variableName.trim()];
|
|
return variable !== undefined ? variable : match;
|
|
});
|
|
}
|
|
|
|
// Helper function to substitute variables in a JSON object or string
|
|
function substituteVariablesInObject(obj, variables) {
|
|
if (typeof obj === 'string') {
|
|
try {
|
|
// If it's a JSON string, parse it, substitute, then stringify
|
|
const parsed = JSON.parse(obj);
|
|
return JSON.stringify(substituteVariablesInObject(parsed, variables));
|
|
} catch (e) {
|
|
// If it's not valid JSON, treat as regular string
|
|
return substituteVariables(obj, variables);
|
|
}
|
|
}
|
|
|
|
if (typeof obj !== 'object' || obj === null) {
|
|
return obj;
|
|
}
|
|
|
|
// Handle arrays
|
|
if (Array.isArray(obj)) {
|
|
return obj.map(item => substituteVariablesInObject(item, variables));
|
|
}
|
|
|
|
// Handle objects
|
|
const result = {};
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
result[key] = substituteVariablesInObject(value, variables);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Execute an API call based on node configuration
|
|
export async function executeApiCall(nodeConfig, processVariables) {
|
|
const {
|
|
apiMethod = 'GET',
|
|
apiUrl = '',
|
|
requestBody = '',
|
|
headers = '{ "Content-Type": "application/json" }',
|
|
outputVariable = 'apiResponse',
|
|
errorVariable = 'apiError',
|
|
continueOnError = false
|
|
} = nodeConfig;
|
|
|
|
// Combine global and process variables
|
|
const allVariables = {
|
|
...processVariables.global || {},
|
|
...processVariables.process || {},
|
|
...processVariables
|
|
};
|
|
|
|
// Substitute variables in URL
|
|
const processedUrl = substituteVariables(apiUrl, allVariables);
|
|
|
|
// Parse and process headers
|
|
let processedHeaders = {};
|
|
try {
|
|
if (typeof headers === 'string') {
|
|
processedHeaders = JSON.parse(headers);
|
|
} else if (typeof headers === 'object' && headers !== null) {
|
|
processedHeaders = headers;
|
|
}
|
|
|
|
// Substitute variables in headers
|
|
processedHeaders = substituteVariablesInObject(processedHeaders, allVariables);
|
|
} catch (error) {
|
|
console.error('Error processing headers:', error);
|
|
processedHeaders = { 'Content-Type': 'application/json' };
|
|
}
|
|
|
|
// Prepare request options
|
|
const options = {
|
|
method: apiMethod,
|
|
headers: processedHeaders
|
|
};
|
|
|
|
// Add request body for appropriate methods
|
|
if (['POST', 'PUT', 'PATCH'].includes(apiMethod) && requestBody) {
|
|
let processedBody;
|
|
|
|
try {
|
|
// Try to parse as JSON if it's a string
|
|
if (typeof requestBody === 'string') {
|
|
const bodyObj = JSON.parse(requestBody);
|
|
processedBody = substituteVariablesInObject(bodyObj, allVariables);
|
|
options.body = JSON.stringify(processedBody);
|
|
} else {
|
|
// If it's already an object
|
|
processedBody = substituteVariablesInObject(requestBody, allVariables);
|
|
options.body = JSON.stringify(processedBody);
|
|
}
|
|
} catch (error) {
|
|
// If not valid JSON, treat as string with variable substitution
|
|
options.body = substituteVariables(requestBody, allVariables);
|
|
}
|
|
}
|
|
|
|
// Create result object
|
|
const result = {
|
|
outputVariable,
|
|
errorVariable,
|
|
continueOnError,
|
|
outputScope: 'global', // Specify that output should go to global variables
|
|
success: false,
|
|
data: null,
|
|
error: null
|
|
};
|
|
|
|
try {
|
|
// Make the API call
|
|
const response = await fetch(processedUrl, options);
|
|
|
|
// Get response data
|
|
let responseData;
|
|
const contentType = response.headers.get('content-type');
|
|
|
|
if (contentType && contentType.includes('application/json')) {
|
|
responseData = await response.json();
|
|
} else {
|
|
responseData = await response.text();
|
|
}
|
|
|
|
// Store result data
|
|
result.success = response.ok;
|
|
result.data = {
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
headers: Object.fromEntries([...response.headers.entries()]),
|
|
data: responseData
|
|
};
|
|
|
|
// If not successful, also store as error
|
|
if (!response.ok) {
|
|
result.error = {
|
|
message: `API call failed with status ${response.status}`,
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
data: responseData
|
|
};
|
|
}
|
|
} catch (error) {
|
|
// Handle network or other errors
|
|
result.success = false;
|
|
result.error = {
|
|
message: error.message || 'Unknown error occurred during API call',
|
|
stack: error.stack,
|
|
name: error.name
|
|
};
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export default {
|
|
executeApiCall
|
|
};
|