UNPKG

n8n

Version:

n8n Workflow Automation Tool

505 lines 25.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ActiveWorkflowManager = void 0; const typedi_1 = require("typedi"); const n8n_core_1 = require("n8n-core"); const n8n_workflow_1 = require("n8n-workflow"); const WebhookHelpers = __importStar(require("./webhooks/WebhookHelpers")); const WorkflowExecuteAdditionalData = __importStar(require("./WorkflowExecuteAdditionalData")); const ActiveExecutions_1 = require("./ActiveExecutions"); const execution_service_1 = require("./executions/execution.service"); const constants_1 = require("./constants"); const NodeTypes_1 = require("./NodeTypes"); const ExternalHooks_1 = require("./ExternalHooks"); const webhook_service_1 = require("./webhooks/webhook.service"); const Logger_1 = require("./Logger"); const workflow_repository_1 = require("./databases/repositories/workflow.repository"); const orchestration_service_1 = require("./services/orchestration.service"); const ActivationErrors_service_1 = require("./ActivationErrors.service"); const activeWorkflows_service_1 = require("./services/activeWorkflows.service"); const workflowExecution_service_1 = require("./workflows/workflowExecution.service"); const workflowStaticData_service_1 = require("./workflows/workflowStaticData.service"); const OnShutdown_1 = require("./decorators/OnShutdown"); let ActiveWorkflowManager = class ActiveWorkflowManager { constructor(logger, activeWorkflows, activeExecutions, externalHooks, nodeTypes, webhookService, workflowRepository, orchestrationService, activationErrorsService, executionService, workflowStaticDataService, activeWorkflowsService, workflowExecutionService) { this.logger = logger; this.activeWorkflows = activeWorkflows; this.activeExecutions = activeExecutions; this.externalHooks = externalHooks; this.nodeTypes = nodeTypes; this.webhookService = webhookService; this.workflowRepository = workflowRepository; this.orchestrationService = orchestrationService; this.activationErrorsService = activationErrorsService; this.executionService = executionService; this.workflowStaticDataService = workflowStaticDataService; this.activeWorkflowsService = activeWorkflowsService; this.workflowExecutionService = workflowExecutionService; this.queuedActivations = {}; } async init() { await this.orchestrationService.init(); await this.addActiveWorkflows('init'); await this.externalHooks.run('activeWorkflows.initialized', []); await this.webhookService.populateCache(); } async getAllWorkflowActivationErrors() { return await this.activationErrorsService.getAll(); } async removeAll() { let activeWorkflowIds = []; this.logger.verbose('Call to remove all active workflows received (removeAll)'); activeWorkflowIds.push(...this.activeWorkflows.allActiveWorkflows()); const activeWorkflows = await this.activeWorkflowsService.getAllActiveIdsInStorage(); activeWorkflowIds = [...activeWorkflowIds, ...activeWorkflows]; activeWorkflowIds = Array.from(new Set(activeWorkflowIds)); const removePromises = []; for (const workflowId of activeWorkflowIds) { removePromises.push(this.remove(workflowId)); } await Promise.all(removePromises); } allActiveInMemory() { return this.activeWorkflows.allActiveWorkflows(); } async isActive(workflowId) { const workflow = await this.workflowRepository.findOne({ select: ['active'], where: { id: workflowId }, }); return !!(workflow === null || workflow === void 0 ? void 0 : workflow.active); } async addWebhooks(workflow, additionalData, mode, activation) { const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true); let path = ''; if (webhooks.length === 0) return; this.logger.debug(`Adding webhooks for workflow "${workflow.name}" (ID ${workflow.id})`); for (const webhookData of webhooks) { const node = workflow.getNode(webhookData.node); node.name = webhookData.node; path = webhookData.path; const webhook = this.webhookService.createWebhook({ workflowId: webhookData.workflowId, webhookPath: path, node: node.name, method: webhookData.httpMethod, }); if (webhook.webhookPath.startsWith('/')) { webhook.webhookPath = webhook.webhookPath.slice(1); } if (webhook.webhookPath.endsWith('/')) { webhook.webhookPath = webhook.webhookPath.slice(0, -1); } if ((path.startsWith(':') || path.includes('/:')) && node.webhookId) { webhook.webhookId = node.webhookId; webhook.pathLength = webhook.webhookPath.split('/').length; } try { await this.webhookService.storeWebhook(webhook); await workflow.createWebhookIfNotExists(webhookData, n8n_core_1.NodeExecuteFunctions, mode, activation); } catch (error) { if (activation === 'init' && error.name === 'QueryFailedError') { continue; } try { await this.clearWebhooks(workflow.id); } catch (error1) { n8n_workflow_1.ErrorReporterProxy.error(error1); this.logger.error(`Could not remove webhooks of workflow "${workflow.id}" because of error: "${error1.message}"`); } if (error instanceof Error && error.name === 'QueryFailedError') { error = new n8n_workflow_1.WebhookPathTakenError(webhook.node, error); } else if (error.detail) { error.message = error.detail; } throw error; } } await this.webhookService.populateCache(); await this.workflowStaticDataService.saveStaticData(workflow); } async clearWebhooks(workflowId) { const workflowData = await this.workflowRepository.findOne({ where: { id: workflowId }, }); if (workflowData === null) { throw new n8n_workflow_1.ApplicationError('Could not find workflow', { extra: { workflowId } }); } const workflow = new n8n_workflow_1.Workflow({ id: workflowId, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes: this.nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings, }); const mode = 'internal'; const additionalData = await WorkflowExecuteAdditionalData.getBase(); const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true); for (const webhookData of webhooks) { await workflow.deleteWebhook(webhookData, n8n_core_1.NodeExecuteFunctions, mode, 'update'); } await this.workflowStaticDataService.saveStaticData(workflow); await this.webhookService.deleteWorkflowWebhooks(workflowId); } getExecutePollFunctions(workflowData, additionalData, mode, activation) { return (workflow, node) => { const returnFunctions = n8n_core_1.NodeExecuteFunctions.getExecutePollFunctions(workflow, node, additionalData, mode, activation); returnFunctions.__emit = (data, responsePromise, donePromise) => { this.logger.debug(`Received event to trigger execution for workflow "${workflow.name}"`); void this.workflowStaticDataService.saveStaticData(workflow); const executePromise = this.workflowExecutionService.runWorkflow(workflowData, node, data, additionalData, mode, responsePromise); if (donePromise) { void executePromise.then((executionId) => { this.activeExecutions .getPostExecutePromise(executionId) .then(donePromise.resolve) .catch(donePromise.reject); }); } else { void executePromise.catch((error) => this.logger.error(error.message, { error })); } }; returnFunctions.__emitError = (error) => { void this.executionService .createErrorExecution(error, node, workflowData, workflow, mode) .then(() => { this.executeErrorWorkflow(error, workflowData, mode); }); }; return returnFunctions; }; } getExecuteTriggerFunctions(workflowData, additionalData, mode, activation) { return (workflow, node) => { const returnFunctions = n8n_core_1.NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode, activation); returnFunctions.emit = (data, responsePromise, donePromise) => { this.logger.debug(`Received trigger for workflow "${workflow.name}"`); void this.workflowStaticDataService.saveStaticData(workflow); const executePromise = this.workflowExecutionService.runWorkflow(workflowData, node, data, additionalData, mode, responsePromise); if (donePromise) { void executePromise.then((executionId) => { this.activeExecutions .getPostExecutePromise(executionId) .then(donePromise.resolve) .catch(donePromise.reject); }); } else { executePromise.catch((error) => this.logger.error(error.message, { error })); } }; returnFunctions.emitError = (error) => { this.logger.info(`The trigger node "${node.name}" of workflow "${workflowData.name}" failed with the error: "${error.message}". Will try to reactivate.`, { nodeName: node.name, workflowId: workflowData.id, workflowName: workflowData.name, }); void this.activeWorkflows.remove(workflowData.id); void this.activationErrorsService.register(workflowData.id, error.message); const activationError = new n8n_workflow_1.WorkflowActivationError(`There was a problem with the trigger node "${node.name}", for that reason did the workflow had to be deactivated`, { cause: error, node }); this.executeErrorWorkflow(activationError, workflowData, mode); this.addQueuedWorkflowActivation(activation, workflowData); }; return returnFunctions; }; } executeErrorWorkflow(error, workflowData, mode) { const fullRunData = { data: { resultData: { error, runData: {}, }, }, finished: false, mode, startedAt: new Date(), stoppedAt: new Date(), status: 'running', }; WorkflowExecuteAdditionalData.executeErrorWorkflow(workflowData, fullRunData, mode); } async addActiveWorkflows(activationMode) { const dbWorkflows = await this.workflowRepository.getAllActive(); if (dbWorkflows.length === 0) return; if (this.orchestrationService.isLeader) { this.logger.info(' ================================'); this.logger.info(' Start Active Workflows:'); this.logger.info(' ================================'); } for (const dbWorkflow of dbWorkflows) { try { const wasActivated = await this.add(dbWorkflow.id, activationMode, dbWorkflow, { shouldPublish: false, }); if (wasActivated) { this.logger.verbose(`Successfully started workflow ${dbWorkflow.display()}`, { workflowName: dbWorkflow.name, workflowId: dbWorkflow.id, }); this.logger.info(' => Started'); } } catch (error) { n8n_workflow_1.ErrorReporterProxy.error(error); this.logger.info(' => ERROR: Workflow could not be activated on first try, keep on trying if not an auth issue'); this.logger.info(` ${error.message}`); this.logger.error(`Issue on initial workflow activation try of ${dbWorkflow.display()} (startup)`, { workflowName: dbWorkflow.name, workflowId: dbWorkflow.id, }); this.executeErrorWorkflow(error, dbWorkflow, 'internal'); if (error.message.includes('Authorization')) continue; this.addQueuedWorkflowActivation('init', dbWorkflow); } } this.logger.verbose('Finished activating workflows (startup)'); } async clearAllActivationErrors() { this.logger.debug('Clearing all activation errors'); await this.activationErrorsService.clearAll(); } async addAllTriggerAndPollerBasedWorkflows() { this.logger.debug('Adding all trigger- and poller-based workflows'); await this.addActiveWorkflows('leadershipChange'); } async removeAllTriggerAndPollerBasedWorkflows() { this.logger.debug('Removing all trigger- and poller-based workflows'); await this.activeWorkflows.removeAllTriggerAndPollerBasedWorkflows(); } async add(workflowId, activationMode, existingWorkflow, { shouldPublish } = { shouldPublish: true }) { if (this.orchestrationService.isMultiMainSetupEnabled && shouldPublish) { await this.orchestrationService.publish('add-webhooks-triggers-and-pollers', { workflowId, }); return; } let workflow; const shouldAddWebhooks = this.orchestrationService.shouldAddWebhooks(activationMode); const shouldAddTriggersAndPollers = this.orchestrationService.shouldAddTriggersAndPollers(); const shouldDisplayActivationMessage = (shouldAddWebhooks || shouldAddTriggersAndPollers) && ['init', 'leadershipChange'].includes(activationMode); try { const dbWorkflow = existingWorkflow !== null && existingWorkflow !== void 0 ? existingWorkflow : (await this.workflowRepository.findById(workflowId)); if (!dbWorkflow) { throw new n8n_workflow_1.WorkflowActivationError(`Failed to find workflow with ID "${workflowId}"`, { level: 'warning', }); } if (shouldDisplayActivationMessage) { this.logger.info(` - ${dbWorkflow.display()}`); this.logger.debug(`Initializing active workflow ${dbWorkflow.display()} (startup)`, { workflowName: dbWorkflow.name, workflowId: dbWorkflow.id, }); } workflow = new n8n_workflow_1.Workflow({ id: dbWorkflow.id, name: dbWorkflow.name, nodes: dbWorkflow.nodes, connections: dbWorkflow.connections, active: dbWorkflow.active, nodeTypes: this.nodeTypes, staticData: dbWorkflow.staticData, settings: dbWorkflow.settings, }); const canBeActivated = workflow.checkIfWorkflowCanBeActivated(constants_1.STARTING_NODES); if (!canBeActivated) { throw new n8n_workflow_1.WorkflowActivationError(`Workflow ${dbWorkflow.display()} has no node to start the workflow - at least one trigger, poller or webhook node is required`, { level: 'warning' }); } const additionalData = await WorkflowExecuteAdditionalData.getBase(); if (shouldAddWebhooks) { await this.addWebhooks(workflow, additionalData, 'trigger', activationMode); } if (shouldAddTriggersAndPollers) { await this.addTriggersAndPollers(dbWorkflow, workflow, { activationMode, executionMode: 'trigger', additionalData, }); } this.removeQueuedWorkflowActivation(workflowId); await this.activationErrorsService.deregister(workflowId); const triggerCount = this.countTriggers(workflow, additionalData); await this.workflowRepository.updateWorkflowTriggerCount(workflow.id, triggerCount); } catch (e) { const error = e instanceof Error ? e : new Error(`${e}`); await this.activationErrorsService.register(workflowId, error.message); throw e; } await this.workflowStaticDataService.saveStaticData(workflow); return shouldDisplayActivationMessage; } countTriggers(workflow, additionalData) { const triggerFilter = (nodeType) => !!nodeType.trigger && !nodeType.description.name.includes('manualTrigger'); return (workflow.queryNodes(triggerFilter).length + workflow.getPollNodes().length + WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, undefined, true).length); } addQueuedWorkflowActivation(activationMode, workflowData) { const workflowId = workflowData.id; const workflowName = workflowData.name; const retryFunction = async () => { this.logger.info(`Try to activate workflow "${workflowName}" (${workflowId})`, { workflowId, workflowName, }); try { await this.add(workflowId, activationMode, workflowData); } catch (error) { n8n_workflow_1.ErrorReporterProxy.error(error); let lastTimeout = this.queuedActivations[workflowId].lastTimeout; if (lastTimeout < constants_1.WORKFLOW_REACTIVATE_MAX_TIMEOUT) { lastTimeout = Math.min(lastTimeout * 2, constants_1.WORKFLOW_REACTIVATE_MAX_TIMEOUT); } this.logger.info(` -> Activation of workflow "${workflowName}" (${workflowId}) did fail with error: "${error.message}" | retry in ${Math.floor(lastTimeout / 1000)} seconds`, { workflowId, workflowName, }); this.queuedActivations[workflowId].lastTimeout = lastTimeout; this.queuedActivations[workflowId].timeout = setTimeout(retryFunction, lastTimeout); return; } this.logger.info(` -> Activation of workflow "${workflowName}" (${workflowId}) was successful!`, { workflowId, workflowName, }); }; this.removeQueuedWorkflowActivation(workflowId); this.queuedActivations[workflowId] = { activationMode, lastTimeout: constants_1.WORKFLOW_REACTIVATE_INITIAL_TIMEOUT, timeout: setTimeout(retryFunction, constants_1.WORKFLOW_REACTIVATE_INITIAL_TIMEOUT), workflowData, }; } removeQueuedWorkflowActivation(workflowId) { if (this.queuedActivations[workflowId]) { clearTimeout(this.queuedActivations[workflowId].timeout); delete this.queuedActivations[workflowId]; } } removeAllQueuedWorkflowActivations() { for (const workflowId in this.queuedActivations) { this.removeQueuedWorkflowActivation(workflowId); } } async remove(workflowId) { if (this.orchestrationService.isMultiMainSetupEnabled) { try { await this.clearWebhooks(workflowId); } catch (error) { n8n_workflow_1.ErrorReporterProxy.error(error); this.logger.error(`Could not remove webhooks of workflow "${workflowId}" because of error: "${error.message}"`); } await this.orchestrationService.publish('remove-triggers-and-pollers', { workflowId }); return; } try { await this.clearWebhooks(workflowId); } catch (error) { n8n_workflow_1.ErrorReporterProxy.error(error); this.logger.error(`Could not remove webhooks of workflow "${workflowId}" because of error: "${error.message}"`); } await this.activationErrorsService.deregister(workflowId); if (this.queuedActivations[workflowId] !== undefined) { this.removeQueuedWorkflowActivation(workflowId); } await this.removeWorkflowTriggersAndPollers(workflowId); } async removeWorkflowTriggersAndPollers(workflowId) { if (!this.activeWorkflows.isActive(workflowId)) return; const wasRemoved = await this.activeWorkflows.remove(workflowId); if (wasRemoved) { this.logger.warn(`Removed triggers and pollers for workflow "${workflowId}"`, { workflowId, }); } } async addTriggersAndPollers(dbWorkflow, workflow, { activationMode, executionMode, additionalData, }) { const getTriggerFunctions = this.getExecuteTriggerFunctions(dbWorkflow, additionalData, executionMode, activationMode); const getPollFunctions = this.getExecutePollFunctions(dbWorkflow, additionalData, executionMode, activationMode); if (workflow.getTriggerNodes().length !== 0 || workflow.getPollNodes().length !== 0) { this.logger.debug(`Adding triggers and pollers for workflow ${dbWorkflow.display()}`); await this.activeWorkflows.add(workflow.id, workflow, additionalData, executionMode, activationMode, getTriggerFunctions, getPollFunctions); this.logger.verbose(`Workflow ${dbWorkflow.display()} activated`, { workflowId: dbWorkflow.id, workflowName: dbWorkflow.name, }); } } async removeActivationError(workflowId) { await this.activationErrorsService.deregister(workflowId); } }; exports.ActiveWorkflowManager = ActiveWorkflowManager; __decorate([ (0, OnShutdown_1.OnShutdown)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], ActiveWorkflowManager.prototype, "removeAllTriggerAndPollerBasedWorkflows", null); exports.ActiveWorkflowManager = ActiveWorkflowManager = __decorate([ (0, typedi_1.Service)(), __metadata("design:paramtypes", [Logger_1.Logger, n8n_core_1.ActiveWorkflows, ActiveExecutions_1.ActiveExecutions, ExternalHooks_1.ExternalHooks, NodeTypes_1.NodeTypes, webhook_service_1.WebhookService, workflow_repository_1.WorkflowRepository, orchestration_service_1.OrchestrationService, ActivationErrors_service_1.ActivationErrorsService, execution_service_1.ExecutionService, workflowStaticData_service_1.WorkflowStaticDataService, activeWorkflows_service_1.ActiveWorkflowsService, workflowExecution_service_1.WorkflowExecutionService]) ], ActiveWorkflowManager); //# sourceMappingURL=ActiveWorkflowManager.js.map