n8n-nodes-clay
Version:
n8n community node for Clay - Data enrichment and automation platform
199 lines (177 loc) • 7.3 kB
JavaScript
// Generic functions for Clay API integration
/**
* Make an API request to Clay
*/
async function clayApiRequest(method, endpoint, body = {}, qs = {}) {
console.log('Clay API Request:', {
method,
url: `https://api.clay.com/v3${endpoint}`,
qs,
body,
});
const options = {
method,
url: `https://api.clay.com/v3${endpoint}`,
headers: {
'Accept': 'application/json',
},
qs: {
...qs,
},
body,
json: true,
};
// Only set Content-Type and include body for requests that have data
if (Object.keys(body).length > 0) {
options.headers['Content-Type'] = 'application/json';
} else {
// Remove body for requests without data (like GET)
delete options.body;
}
try {
return await this.helpers.httpRequestWithAuthentication.call(this, 'clayApi', options);
} catch (error) {
const err = error;
// Handle different types of errors
if (err.response) {
const statusCode = err.response.statusCode;
const responseBody = err.response.body;
// Handle specific HTTP status codes
switch (statusCode) {
case 401:
throw new Error('Clay API authentication failed. Please check your API key in the credentials.');
case 403:
throw new Error('Clay API access forbidden. Please verify your API key has the necessary permissions.');
case 404:
throw new Error('Clay API endpoint not found. The requested resource may not exist or the API version may be incorrect.');
case 429:
throw new Error('Clay API rate limit exceeded. Please wait before making more requests.');
case 500:
throw new Error('Clay API server error. Please try again later or contact Clay support.');
default:
if (responseBody && responseBody.message) {
throw new Error(`Clay API error [${statusCode}]: ${responseBody.message}`);
} else if (responseBody && responseBody.error) {
throw new Error(`Clay API error [${statusCode}]: ${responseBody.error}`);
} else {
throw new Error(`Clay API error [${statusCode}]: ${err.message || 'Unknown error'}`);
}
}
} else if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED') {
throw new Error('Unable to connect to Clay API. Please check your internet connection.');
} else if (err.code === 'ETIMEDOUT') {
throw new Error('Clay API request timed out. Please try again.');
} else {
throw new Error(`Clay API request failed: ${err.message || 'Unknown error'}`);
}
}
}
/**
* Make a webhook request to Clay table
*/
async function clayWebhookRequest(webhookUrl, body = {}) {
console.log('Clay Webhook Request:', {
url: webhookUrl,
body,
});
const options = {
method: 'POST',
url: webhookUrl,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body,
json: true,
};
try {
return await this.helpers.request(options);
} catch (error) {
const err = error;
// Handle webhook-specific errors
if (err.response) {
const statusCode = err.response.statusCode;
const responseBody = err.response.body;
switch (statusCode) {
case 400:
throw new Error('Clay webhook bad request. Please check your field names and data format match the table schema.');
case 401:
throw new Error('Clay webhook authentication failed. Please verify the webhook URL is correct.');
case 403:
throw new Error('Clay webhook access forbidden. The webhook may be disabled or you may not have permission.');
case 404:
throw new Error('Clay webhook not found. Please verify the webhook URL is correct and the table exists.');
case 413:
throw new Error('Clay webhook payload too large. Please reduce the amount of data being sent.');
case 429:
throw new Error('Clay webhook rate limit exceeded. Please wait before sending more data.');
case 500:
throw new Error('Clay webhook server error. Please try again later or contact Clay support.');
default:
if (responseBody && responseBody.message) {
throw new Error(`Clay webhook error [${statusCode}]: ${responseBody.message}`);
} else if (responseBody && responseBody.error) {
throw new Error(`Clay webhook error [${statusCode}]: ${responseBody.error}`);
} else {
throw new Error(`Clay webhook error [${statusCode}]: ${err.message || 'Unknown error'}`);
}
}
} else if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED') {
throw new Error('Unable to connect to Clay webhook. Please check the webhook URL and your internet connection.');
} else if (err.code === 'ETIMEDOUT') {
throw new Error('Clay webhook request timed out. Please try again.');
} else {
throw new Error(`Clay webhook request failed: ${err.message || 'Unknown error'}`);
}
}
}
/**
* Validate Clay webhook URL format
*/
function validateWebhookUrl(webhookUrl) {
const webhookPattern = /^https:\/\/api\.clay\.com\/v3\/sources\/webhook\/pull-in-data-from-a-webhook-[a-f0-9-]+$/i;
return webhookPattern.test(webhookUrl);
}
/**
* Validate field name (no empty strings, reasonable length)
*/
function validateFieldName(fieldName) {
return typeof fieldName === 'string' &&
fieldName.trim().length > 0 &&
fieldName.length <= 100;
}
/**
* Sanitize field value for Clay webhook
*/
function sanitizeFieldValue(value) {
if (value === null || value === undefined) {
return '';
}
if (typeof value === 'object') {
return JSON.stringify(value);
}
return String(value);
}
/**
* Validate and sanitize record data for Clay webhook
*/
function validateAndSanitizeRecordData(recordData) {
const sanitized = {};
for (const [fieldName, fieldValue] of Object.entries(recordData)) {
// Validate field name
if (!validateFieldName(fieldName)) {
throw new Error(`Invalid field name: "${fieldName}". Field names must be non-empty strings with reasonable length.`);
}
// Sanitize field value
sanitized[fieldName] = sanitizeFieldValue(fieldValue);
}
return sanitized;
}
module.exports = {
clayApiRequest,
clayWebhookRequest,
validateWebhookUrl,
validateFieldName,
sanitizeFieldValue,
validateAndSanitizeRecordData
};