n8n-nodes-heyreach
Version:
n8n community node for HeyReach API - LinkedIn automation platform
309 lines • 15.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CampaignOperations = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const generic_functions_1 = require("../../generic.functions");
/**
* Campaign operations for HeyReach API
*/
class CampaignOperations {
/**
* Get a single campaign by ID
*/
static async get(context, itemIndex) {
const campaignId = this.getCampaignId(context, itemIndex);
const response = await generic_functions_1.heyReachApiRequest.call(context, 'GET', `/campaign/GetById?campaignId=${campaignId}`);
// The GetById endpoint returns campaign data directly in response.data
// (not wrapped in an items array like GetAll does)
if (response && typeof response === 'object') {
return response;
}
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Campaign with ID ${campaignId} not found`, { itemIndex });
}
/**
* Get many campaigns with pagination support
*/
static async getMany(context, itemIndex) {
const returnAll = context.getNodeParameter('returnAll', itemIndex, false);
const limit = context.getNodeParameter('limit', itemIndex, 50);
const additionalFields = context.getNodeParameter('additionalFields', itemIndex, {});
// Validate limit doesn't exceed 100
if (limit > 100) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Limit cannot exceed 100. HeyReach API has a maximum limit of 100.', { itemIndex });
}
// Build request body
const body = {
offset: 0,
limit: returnAll ? 100 : limit,
};
// Add optional filters
if (additionalFields.keyword) {
body.keyword = additionalFields.keyword;
}
if (additionalFields.statuses && additionalFields.statuses.length > 0) {
body.statuses = additionalFields.statuses;
}
if (additionalFields.accountIds) {
// Parse comma-separated account IDs
const accountIds = additionalFields.accountIds
.split(',')
.map((id) => parseInt(id.trim(), 10))
.filter((id) => !isNaN(id));
if (accountIds.length > 0) {
body.accountIds = accountIds;
}
}
if (returnAll) {
const campaigns = await generic_functions_1.heyReachApiRequestAllItems.call(context, 'POST', '/campaign/GetAll', body);
return campaigns;
}
else {
const response = await generic_functions_1.heyReachApiRequest.call(context, 'POST', '/campaign/GetAll', body);
return response.items || [];
}
}
/**
* Pause a campaign
* Note: HeyReach API sometimes returns 400 "You cannot pause an inactive campaign"
* but still successfully pauses the campaign. We handle this inconsistency.
*/
static async pause(context, itemIndex) {
const campaignId = this.getCampaignId(context, itemIndex);
try {
const response = await generic_functions_1.heyReachApiRequest.call(context, 'POST', `/campaign/Pause?campaignId=${campaignId}`);
// Check if response has the expected structure
if (response && typeof response === 'object') {
// If response has items array, use first item
if (response.items && Array.isArray(response.items) && response.items.length > 0) {
return response.items[0];
}
// If response is the campaign object itself
if (response.id) {
return response;
}
}
// Fallback: return basic campaign info
return { id: campaignId, status: 'PAUSED' };
}
catch (error) {
// Handle the specific HeyReach API inconsistency
if (error.message && error.message.includes('You cannot pause an inactive campaign')) {
console.log(`⚠️ HeyReach API returned pause error, but checking if campaign was actually paused...`);
try {
// Check the current campaign status
const currentCampaign = await this.get(context, itemIndex);
if (currentCampaign.status === 'PAUSED') {
console.log(`✅ Campaign ${campaignId} was successfully paused despite API error`);
return currentCampaign;
}
else {
console.log(`❌ Campaign ${campaignId} was not paused. Current status: ${currentCampaign.status}`);
// Re-throw the original error since the campaign wasn't actually paused
throw error;
}
}
catch (statusCheckError) {
console.log(`❌ Failed to verify campaign status after pause error:`, statusCheckError);
// Re-throw the original pause error
throw error;
}
}
// For any other errors, re-throw them
throw error;
}
}
/**
* Resume a campaign
* Note: HeyReach API might have similar inconsistencies as pause operation
*/
static async resume(context, itemIndex) {
const campaignId = this.getCampaignId(context, itemIndex);
try {
const response = await generic_functions_1.heyReachApiRequest.call(context, 'POST', `/campaign/Resume?campaignId=${campaignId}`);
// Check if response has the expected structure
if (response && typeof response === 'object') {
// If response has items array, use first item
if (response.items && Array.isArray(response.items) && response.items.length > 0) {
return response.items[0];
}
// If response is the campaign object itself
if (response.id) {
return response;
}
}
// Fallback: return basic campaign info
return { id: campaignId, status: 'IN_PROGRESS' };
}
catch (error) {
// Handle potential HeyReach API inconsistencies for resume as well
if (error.message && (error.message.includes('cannot resume') || error.message.includes('inactive campaign'))) {
console.log(`⚠️ HeyReach API returned resume error, but checking if campaign was actually resumed...`);
try {
// Check the current campaign status
const currentCampaign = await this.get(context, itemIndex);
if (currentCampaign.status === 'IN_PROGRESS') {
console.log(`✅ Campaign ${campaignId} was successfully resumed despite API error`);
return currentCampaign;
}
else {
console.log(`❌ Campaign ${campaignId} was not resumed. Current status: ${currentCampaign.status}`);
// Re-throw the original error since the campaign wasn't actually resumed
throw error;
}
}
catch (statusCheckError) {
console.log(`❌ Failed to verify campaign status after resume error:`, statusCheckError);
// Re-throw the original resume error
throw error;
}
}
// For any other errors, re-throw them
throw error;
}
}
/**
* Add leads to a campaign
*/
static async addLeads(context, itemIndex) {
const campaignId = this.getCampaignId(context, itemIndex);
const leadsInputMode = context.getNodeParameter('leadsInputMode', itemIndex);
// First, validate the campaign status
const campaign = await this.get(context, itemIndex);
(0, generic_functions_1.validateCampaignStatus)(campaign, 'add leads');
let accountLeadPairs = [];
if (leadsInputMode === 'single') {
// Single lead input
const profileUrl = context.getNodeParameter('profileUrl', itemIndex);
const firstName = context.getNodeParameter('firstName', itemIndex);
const lastName = context.getNodeParameter('lastName', itemIndex);
const additionalLeadFields = context.getNodeParameter('additionalLeadFields', itemIndex, {});
// Validate LinkedIn profile URL
if (!(0, generic_functions_1.validateLinkedInProfileUrl)(profileUrl)) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Invalid LinkedIn profile URL format. Please use format: https://www.linkedin.com/in/username', { itemIndex });
}
// Build lead object
const lead = {
profileUrl,
firstName,
lastName,
};
// Add optional fields
if (additionalLeadFields.location)
lead.location = additionalLeadFields.location;
if (additionalLeadFields.companyName)
lead.companyName = additionalLeadFields.companyName;
if (additionalLeadFields.position)
lead.position = additionalLeadFields.position;
if (additionalLeadFields.summary)
lead.summary = additionalLeadFields.summary;
if (additionalLeadFields.about)
lead.about = additionalLeadFields.about;
if (additionalLeadFields.emailAddress)
lead.emailAddress = additionalLeadFields.emailAddress;
// Handle custom fields
if (additionalLeadFields.customUserFields && additionalLeadFields.customUserFields.field) {
const customFields = [];
for (const field of additionalLeadFields.customUserFields.field) {
if (field.name && field.value) {
// Validate custom field name format
if (!(0, generic_functions_1.validateCustomFieldName)(field.name)) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid custom field name "${field.name}". Name must contain only alphanumeric characters and underscores.`, { itemIndex });
}
customFields.push({
name: field.name,
value: field.value,
});
}
}
if (customFields.length > 0) {
lead.customUserFields = customFields;
}
}
// Create account lead pair
const accountLeadPair = { lead };
if (additionalLeadFields.linkedInAccountId) {
accountLeadPair.linkedInAccountId = additionalLeadFields.linkedInAccountId;
}
accountLeadPairs = [accountLeadPair];
}
else {
// JSON input mode
const leadsJson = context.getNodeParameter('leadsJson', itemIndex);
try {
const leadsData = JSON.parse(leadsJson);
if (!Array.isArray(leadsData)) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Leads JSON must be an array of lead objects', { itemIndex });
}
// Validate and process each lead
for (const leadData of leadsData) {
if (!leadData.profileUrl || !leadData.firstName || !leadData.lastName) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Each lead must have profileUrl, firstName, and lastName', { itemIndex });
}
// Validate LinkedIn profile URL
if (!(0, generic_functions_1.validateLinkedInProfileUrl)(leadData.profileUrl)) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid LinkedIn profile URL format: ${leadData.profileUrl}`, { itemIndex });
}
// Validate custom fields if present
if (leadData.customUserFields) {
for (const field of leadData.customUserFields) {
if (field.name && !(0, generic_functions_1.validateCustomFieldName)(field.name)) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid custom field name "${field.name}". Name must contain only alphanumeric characters and underscores.`, { itemIndex });
}
}
}
const accountLeadPair = { lead: leadData };
if (leadData.linkedInAccountId) {
accountLeadPair.linkedInAccountId = leadData.linkedInAccountId;
}
accountLeadPairs.push(accountLeadPair);
}
}
catch (error) {
if (error instanceof n8n_workflow_1.NodeOperationError) {
throw error;
}
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid JSON format: ${error.message}`, { itemIndex });
}
}
// Validate we have leads to add
if (accountLeadPairs.length === 0) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'No valid leads to add to campaign', { itemIndex });
}
// Add leads to campaign using V2 endpoint for better response data
const response = await generic_functions_1.heyReachApiRequest.call(context, 'POST', '/campaign/AddLeadsToCampaignV2', {
campaignId,
accountLeadPairs,
});
return {
campaignId,
campaignName: campaign.name,
campaignStatus: campaign.status,
leadsProcessed: accountLeadPairs.length,
addedLeadsCount: response.addedLeadsCount || 0,
updatedLeadsCount: response.updatedLeadsCount || 0,
failedLeadsCount: response.failedLeadsCount || 0,
};
}
/**
* Helper method to get campaign ID from resource locator
*/
static getCampaignId(context, itemIndex) {
const campaignId = context.getNodeParameter('campaignId', itemIndex);
if (typeof campaignId === 'object' && campaignId.value) {
return parseInt(campaignId.value, 10);
}
if (typeof campaignId === 'number') {
return campaignId;
}
if (typeof campaignId === 'string') {
const parsed = parseInt(campaignId, 10);
if (isNaN(parsed)) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid campaign ID: ${campaignId}`, { itemIndex });
}
return parsed;
}
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Campaign ID is required', { itemIndex });
}
}
exports.CampaignOperations = CampaignOperations;
//# sourceMappingURL=CampaignOperations.js.map