n8n-nodes-sevdesk-v2
Version:
n8n community node for SevDesk API v2 integration with 24 production-ready workflow templates. Direct API access without external dependencies - simplified, secure, and optimized for German accounting automation (n8n 1.101.1 compatible).
233 lines (232 loc) • 9.08 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SevDeskApiClient = void 0;
const SevDeskErrors_1 = require("./errors/SevDeskErrors");
const SevDeskConfig_1 = require("./config/SevDeskConfig");
const EnvironmentConfig_1 = require("./config/EnvironmentConfig");
class SevDeskApiClient {
constructor(executeFunctions) {
this.baseURL = '';
this.executeFunctions = executeFunctions;
}
async initialize() {
(0, EnvironmentConfig_1.validateAndWarnEnvironmentConfig)();
this.credentials = await this.executeFunctions.getCredentials('sevDeskApi');
this.baseURL = `https://my.sevdesk.de/api/${this.credentials.apiVersion}`;
}
async request(config) {
if (!this.credentials) {
await this.initialize();
}
const apiVersion = this.credentials.apiVersion || 'v2';
const timeouts = SevDeskConfig_1.SevDeskConfigManager.getTimeouts(apiVersion);
const requestOptions = {
method: config.method,
url: `${this.baseURL}${config.endpoint}`,
headers: {
'Authorization': this.credentials.apiKey,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
timeout: timeouts.request,
};
if (config.queryParams) {
requestOptions.qs = this.processQueryParameters(config.queryParams);
}
if (config.body && (config.method === 'POST' || config.method === 'PUT')) {
requestOptions.body = config.body;
}
try {
this.logRequest(config);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Request timeout after ${timeouts.request}ms`));
}, timeouts.request);
});
const requestPromise = this.executeFunctions.helpers.httpRequest(requestOptions);
const response = await Promise.race([requestPromise, timeoutPromise]);
this.logResponse(config, response);
return {
data: response,
statusCode: response.statusCode,
headers: response.headers,
};
}
catch (error) {
this.logError(config, error);
if (error.message && error.message.includes('timeout')) {
const timeoutError = new Error(`Request timed out after ${timeouts.request}ms. ` +
`This may indicate network issues or that the SevDesk API is experiencing delays. ` +
`Please try again later or check your network connection.`);
timeoutError.name = 'TimeoutError';
throw this.handleApiError(timeoutError, config);
}
throw this.handleApiError(error, config);
}
}
async get(endpoint, queryParams) {
return this.request({
method: 'GET',
endpoint,
queryParams,
});
}
async post(endpoint, body, queryParams) {
return this.request({
method: 'POST',
endpoint,
body,
queryParams,
});
}
async put(endpoint, body, queryParams) {
return this.request({
method: 'PUT',
endpoint,
body,
queryParams,
});
}
async delete(endpoint, queryParams) {
return this.request({
method: 'DELETE',
endpoint,
queryParams,
});
}
processQueryParameters(params) {
const processedParams = {};
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null && value !== '') {
processedParams[key] = value;
}
}
return processedParams;
}
sanitizeForLogging(data) {
if (!data)
return data;
const dataStr = typeof data === 'string' ? data : JSON.stringify(data);
const sensitivePatterns = [
/api[_-]?key[=:\s]*[a-zA-Z0-9_-]{10,}/gi,
/token[=:\s]*[a-zA-Z0-9_-]{10,}/gi,
/bearer\s+[a-zA-Z0-9_-]{10,}/gi,
/authorization[=:\s]*[a-zA-Z0-9_-]{10,}/gi,
/([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g,
/\+?[\d\s\-\(\)]{10,}/g,
/\b\d{4}[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{4}\b/g,
/\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
];
let sanitized = dataStr;
sensitivePatterns.forEach(pattern => {
if (pattern.source.includes('email')) {
sanitized = sanitized.replace(pattern, (match, user, domain) => {
const maskedUser = user.length > 1 ? user[0] + '*'.repeat(Math.min(user.length - 1, 3)) : '*';
return `${maskedUser}@${domain}`;
});
}
else if (pattern.source.includes('IP')) {
sanitized = sanitized.replace(pattern, (match) => {
const parts = match.split('.');
return parts.length === 4 ? `${parts[0]}.${parts[1]}.*.*` : match;
});
}
else {
sanitized = sanitized.replace(pattern, '[REDACTED]');
}
});
if (typeof data === 'object') {
try {
return JSON.parse(sanitized);
}
catch {
return sanitized;
}
}
return sanitized;
}
logRequest(config) {
if (!(0, EnvironmentConfig_1.isDebugEnabled)()) {
return;
}
const logData = {
timestamp: new Date().toISOString(),
method: config.method,
endpoint: this.sanitizeForLogging(config.endpoint),
hasBody: !!config.body,
hasQueryParams: !!config.queryParams,
};
console.info(`[SevDesk API Request] ${JSON.stringify(logData)}`);
}
logResponse(config, response) {
if (!(0, EnvironmentConfig_1.isDebugEnabled)()) {
return;
}
const logData = {
timestamp: new Date().toISOString(),
method: config.method,
endpoint: this.sanitizeForLogging(config.endpoint),
statusCode: response.statusCode || 'unknown',
hasData: !!response,
};
console.info(`[SevDesk API Response] ${JSON.stringify(logData)}`);
}
logError(config, error) {
const logData = {
timestamp: new Date().toISOString(),
method: config.method,
endpoint: config.endpoint,
error: error instanceof Error ? error.message : String(error),
};
console.error(`[SevDesk API Error] ${JSON.stringify(logData)}`);
}
handleApiError(error, config) {
let sevDeskError;
console.log('Error details:', {
code: error.code,
name: error.name,
message: error.message,
statusCode: error.statusCode,
hasResponse: !!error.response
});
if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND' || error.code === 'ETIMEDOUT' ||
error.name === 'TimeoutError' || error.name === 'ConnectionError' ||
error.message?.includes('timeout') || error.message?.includes('Connection refused')) {
console.log('Creating connection error');
sevDeskError = SevDeskErrors_1.SevDeskErrorFactory.createConnectionError(error);
}
else if (error.statusCode || error.response?.status) {
console.log('Creating HTTP status error');
const statusCode = error.statusCode || error.response?.status;
const errorData = error.response?.data || error.response?.body || error.body || {};
sevDeskError = SevDeskErrors_1.SevDeskErrorFactory.createError(statusCode, errorData, error);
}
else {
console.log('Creating configuration error');
const errorMessage = error instanceof Error ? error.message : String(error);
sevDeskError = SevDeskErrors_1.SevDeskErrorFactory.createConfigurationError(`API request failed: ${errorMessage}`, error);
}
console.log('Created error:', {
type: sevDeskError.constructor.name,
message: sevDeskError.message,
errorCode: sevDeskError.errorCode
});
return sevDeskError.toNodeApiError(this.executeFunctions.getNode());
}
addPagination(queryParams, itemIndex) {
const limit = this.executeFunctions.getNodeParameter('limit', itemIndex, 50);
const offset = this.executeFunctions.getNodeParameter('offset', itemIndex, 0);
return {
...queryParams,
limit,
offset
};
}
getBaseURL() {
return this.baseURL;
}
getCredentials() {
return this.credentials;
}
}
exports.SevDeskApiClient = SevDeskApiClient;