import fetch from 'node-fetch';
import fs from 'fs';
import path from 'path';
/**
* n8n REST API Client
* Provides programmatic access to n8n workflow automation platform
*/
export class N8nClient {
constructor(n8nURL, apiKey = null, username = null, password = null) {
this.baseURL = n8nURL.replace(/\/$/, ''); // Remove trailing slash
this.apiKey = apiKey;
this.username = username;
this.password = password;
this.authToken = null;
}
/**
* Get authentication headers for API requests
*/
getAuthHeaders() {
const headers = {
'Content-Type': 'application/json'
};
if (this.apiKey) {
headers['X-N8N-API-KEY'] = this.apiKey;
} else if (this.authToken) {
headers['Authorization'] = `Bearer ${this.authToken}`;
}
return headers;
}
/**
* Authenticate using username/password (if no API key)
*/
async authenticate() {
if (this.apiKey) {
console.log('✅ Using n8n API key authentication');
return true;
}
if (!this.username || !this.password) {
throw new Error('Either API key or username/password required for n8n authentication');
}
try {
const response = await fetch(`${this.baseURL}/rest/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: this.username,
password: this.password
})
});
if (!response.ok) {
throw new Error(`Authentication failed: ${response.statusText}`);
}
const data = await response.json();
this.authToken = data.data?.token;
if (this.authToken) {
console.log('✅ n8n authentication successful');
return true;
} else {
throw new Error('No token received from n8n');
}
} catch (error) {
console.error('❌ n8n authentication failed:', error.message);
return false;
}
}
/**
* Make authenticated API request
*/
async makeRequest(endpoint, options = {}) {
const url = `${this.baseURL}/rest${endpoint}`;
const headers = {
...this.getAuthHeaders(),
...options.headers
};
try {
const response = await fetch(url, {
...options,
headers
});
if (!response.ok) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return {
success: true,
data: data.data || data
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Get all workflows
*/
async getWorkflows() {
return await this.makeRequest('/workflows');
}
/**
* Get specific workflow by ID
*/
async getWorkflow(workflowId) {
return await this.makeRequest(`/workflows/${workflowId}`);
}
/**
* Create a new workflow
*/
async createWorkflow(workflowData) {
return await this.makeRequest('/workflows', {
method: 'POST',
body: JSON.stringify(workflowData)
});
}
/**
* Update existing workflow
*/
async updateWorkflow(workflowId, workflowData) {
return await this.makeRequest(`/workflows/${workflowId}`, {
method: 'PUT',
body: JSON.stringify(workflowData)
});
}
/**
* Delete workflow
*/
async deleteWorkflow(workflowId) {
return await this.makeRequest(`/workflows/${workflowId}`, {
method: 'DELETE'
});
}
/**
* Activate/deactivate workflow
*/
async setWorkflowStatus(workflowId, active) {
return await this.makeRequest(`/workflows/${workflowId}/${active ? 'activate' : 'deactivate'}`, {
method: 'POST'
});
}
/**
* Execute workflow manually
*/
async executeWorkflow(workflowId, inputData = {}) {
return await this.makeRequest(`/workflows/${workflowId}/execute`, {
method: 'POST',
body: JSON.stringify(inputData)
});
}
/**
* Get workflow execution history
*/
async getExecutions(workflowId = null, limit = 20) {
const params = new URLSearchParams();
if (workflowId) params.append('workflowId', workflowId);
params.append('limit', limit.toString());
return await this.makeRequest(`/executions?${params}`);
}
/**
* Get installed community nodes
*/
async getCommunityNodes() {
return await this.makeRequest('/community-packages');
}
/**
* Install community node package
*/
async installCommunityNode(packageName, version = 'latest') {
const packageSpec = version === 'latest' ? packageName : `${packageName}@${version}`;
return await this.makeRequest('/community-packages', {
method: 'POST',
body: JSON.stringify({
name: packageSpec
})
});
}
/**
* Uninstall community node package
*/
async uninstallCommunityNode(packageName) {
return await this.makeRequest(`/community-packages/${packageName}`, {
method: 'DELETE'
});
}
/**
* Update community node package
*/
async updateCommunityNode(packageName, version = 'latest') {
return await this.makeRequest(`/community-packages/${packageName}`, {
method: 'PATCH',
body: JSON.stringify({
version: version
})
});
}
/**
* Import workflow from JSON file
*/
async importWorkflowFromFile(filePath) {
try {
if (!fs.existsSync(filePath)) {
throw new Error(`Workflow file not found: ${filePath}`);
}
const workflowContent = fs.readFileSync(filePath, 'utf8');
const workflowData = JSON.parse(workflowContent);
// Ensure required fields
if (!workflowData.name) {
workflowData.name = path.basename(filePath, '.json');
}
const result = await this.createWorkflow(workflowData);
return {
...result,
fileName: path.basename(filePath),
imported: true
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Export workflow to JSON file
*/
async exportWorkflowToFile(workflowId, outputPath = null) {
try {
const workflowResult = await this.getWorkflow(workflowId);
if (!workflowResult.success) {
return workflowResult;
}
const workflow = workflowResult.data;
const fileName = outputPath || `${workflow.name || 'workflow'}_${workflowId}.json`;
fs.writeFileSync(fileName, JSON.stringify(workflow, null, 2));
return {
success: true,
message: `Workflow exported successfully`,
fileName: fileName,
workflowName: workflow.name
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Bulk import workflows from directory
*/
async importWorkflowsFromDirectory(directoryPath) {
try {
if (!fs.existsSync(directoryPath)) {
throw new Error(`Directory not found: ${directoryPath}`);
}
const files = fs.readdirSync(directoryPath)
.filter(file => file.endsWith('.json'));
const results = [];
for (const file of files) {
const filePath = path.join(directoryPath, file);
const result = await this.importWorkflowFromFile(filePath);
results.push({
file: file,
...result
});
}
const successful = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
return {
success: failed === 0,
message: `Imported ${successful} workflows successfully, ${failed} failed`,
results: results,
summary: { successful, failed, total: files.length }
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Get available workflow templates from n8n community
*/
async getWorkflowTemplates(category = null, limit = 50) {
try {
// This would connect to n8n's template API
// Note: Actual implementation depends on n8n's template API availability
const params = new URLSearchParams();
if (category) params.append('category', category);
params.append('limit', limit.toString());
return await this.makeRequest(`/workflows/templates?${params}`);
} catch (error) {
return {
success: false,
error: 'Template API not available in this n8n version',
message: 'Consider using workflow import from QNAP storage instead'
};
}
}
/**
* Health check for n8n instance
*/
async healthCheck() {
try {
const response = await fetch(`${this.baseURL}/healthz`);
const isHealthy = response.ok;
return {
success: true,
healthy: isHealthy,
status: response.status,
message: isHealthy ? 'n8n instance is healthy' : 'n8n instance has issues'
};
} catch (error) {
return {
success: false,
healthy: false,
error: error.message
};
}
}
/**
* Get n8n instance information
*/
async getInstanceInfo() {
try {
// Try to get settings/info endpoint
const result = await this.makeRequest('/settings');
return result;
} catch (error) {
return {
success: false,
error: error.message
};
}
}
}
export default N8nClient;