UNPKG

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
"use strict"; 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;