UNPKG

n8n

Version:

n8n Workflow Automation Tool

401 lines 21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkflowRunner = void 0; const n8n_core_1 = require("n8n-core"); const n8n_workflow_1 = require("n8n-workflow"); const PCancelable = require("p-cancelable"); const path_1 = require("path"); const child_process_1 = require("child_process"); const config = require("../config"); const _1 = require("."); const Queue = require("./Queue"); const InternalHooksManager_1 = require("./InternalHooksManager"); class WorkflowRunner { constructor() { this.push = _1.Push.getInstance(); this.activeExecutions = _1.ActiveExecutions.getInstance(); this.credentialsOverwrites = _1.CredentialsOverwrites().getAll(); const executionsMode = config.get('executions.mode'); if (executionsMode === 'queue') { this.jobQueue = Queue.getInstance().getBullObjectInstance(); } } processHookMessage(workflowHooks, hookData) { workflowHooks.executeHookFunctions(hookData.hook, hookData.parameters); } async processError(error, startedAt, executionMode, executionId, hooks) { const fullRunData = { data: { resultData: { error: Object.assign(Object.assign({}, error), { message: error.message, stack: error.stack }), runData: {}, }, }, finished: false, mode: executionMode, startedAt, stoppedAt: new Date(), }; this.activeExecutions.remove(executionId, fullRunData); if (hooks) { await hooks.executeHookFunctions('workflowExecuteAfter', [fullRunData]); } } async run(data, loadStaticData, realtime, executionId, responsePromise) { const executionsProcess = config.get('executions.process'); const executionsMode = config.get('executions.mode'); if (executionsMode === 'queue' && data.executionMode !== 'manual') { executionId = await this.runBull(data, loadStaticData, realtime, executionId, responsePromise); } else if (executionsProcess === 'main') { executionId = await this.runMainProcess(data, loadStaticData, executionId, responsePromise); } else { executionId = await this.runSubprocess(data, loadStaticData, executionId, responsePromise); } const postExecutePromise = this.activeExecutions.getPostExecutePromise(executionId); const externalHooks = _1.ExternalHooks(); postExecutePromise .then(async (executionData) => { void InternalHooksManager_1.InternalHooksManager.getInstance().onWorkflowPostExecute(data.workflowData, executionData); }) .catch((error) => { console.error('There was a problem running internal hook "onWorkflowPostExecute"', error); }); if (externalHooks.exists('workflow.postExecute')) { postExecutePromise .then(async (executionData) => { await externalHooks.run('workflow.postExecute', [executionData, data.workflowData]); }) .catch((error) => { console.error('There was a problem running hook "workflow.postExecute"', error); }); } return executionId; } async runMainProcess(data, loadStaticData, restartExecutionId, responsePromise) { if (loadStaticData === true && data.workflowData.id) { data.workflowData.staticData = await _1.WorkflowHelpers.getStaticDataById(data.workflowData.id); } const nodeTypes = _1.NodeTypes(); let executionTimeout; let workflowTimeout = config.get('executions.timeout'); if (data.workflowData.settings && data.workflowData.settings.executionTimeout) { workflowTimeout = data.workflowData.settings.executionTimeout; } if (workflowTimeout > 0) { workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout')); } const workflow = new n8n_workflow_1.Workflow({ id: data.workflowData.id, name: data.workflowData.name, nodes: data.workflowData.nodes, connections: data.workflowData.connections, active: data.workflowData.active, nodeTypes, staticData: data.workflowData.staticData, }); const additionalData = await _1.WorkflowExecuteAdditionalData.getBase(undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000); const executionId = await this.activeExecutions.add(data, undefined, restartExecutionId); additionalData.executionId = executionId; n8n_workflow_1.LoggerProxy.verbose(`Execution for workflow ${data.workflowData.name} was assigned id ${executionId}`, { executionId }); let workflowExecution; try { n8n_workflow_1.LoggerProxy.verbose(`Execution for workflow ${data.workflowData.name} was assigned id ${executionId}`, { executionId }); additionalData.hooks = _1.WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId, true); additionalData.hooks.hookFunctions.sendResponse = [ async (response) => { if (responsePromise) { responsePromise.resolve(response); } }, ]; additionalData.sendMessageToUI = _1.WorkflowExecuteAdditionalData.sendMessageToUI.bind({ sessionId: data.sessionId, }); if (data.executionData !== undefined) { n8n_workflow_1.LoggerProxy.debug(`Execution ID ${executionId} had Execution data. Running with payload.`, { executionId, }); const workflowExecute = new n8n_core_1.WorkflowExecute(additionalData, data.executionMode, data.executionData); workflowExecution = workflowExecute.processRunExecutionData(workflow); } else if (data.runData === undefined || data.startNodes === undefined || data.startNodes.length === 0 || data.destinationNode === undefined) { n8n_workflow_1.LoggerProxy.debug(`Execution ID ${executionId} will run executing all nodes.`, { executionId }); const workflowExecute = new n8n_core_1.WorkflowExecute(additionalData, data.executionMode); workflowExecution = workflowExecute.run(workflow, undefined, data.destinationNode); } else { n8n_workflow_1.LoggerProxy.debug(`Execution ID ${executionId} is a partial execution.`, { executionId }); const workflowExecute = new n8n_core_1.WorkflowExecute(additionalData, data.executionMode); workflowExecution = workflowExecute.runPartialWorkflow(workflow, data.runData, data.startNodes, data.destinationNode); } this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution); if (workflowTimeout > 0) { const timeout = Math.min(workflowTimeout, config.get('executions.maxTimeout')) * 1000; executionTimeout = setTimeout(() => { this.activeExecutions.stopExecution(executionId, 'timeout'); }, timeout); } workflowExecution .then((fullRunData) => { clearTimeout(executionTimeout); if (workflowExecution.isCanceled) { fullRunData.finished = false; } this.activeExecutions.remove(executionId, fullRunData); }) .catch((error) => { this.processError(error, new Date(), data.executionMode, executionId, additionalData.hooks); }); } catch (error) { await this.processError(error, new Date(), data.executionMode, executionId, additionalData.hooks); throw error; } return executionId; } async runBull(data, loadStaticData, realtime, restartExecutionId, responsePromise) { const executionId = await this.activeExecutions.add(data, undefined, restartExecutionId); if (responsePromise) { this.activeExecutions.attachResponsePromise(executionId, responsePromise); } const jobData = { executionId, loadStaticData: !!loadStaticData, }; let priority = 100; if (realtime === true) { priority = 50; } const jobOptions = { priority, removeOnComplete: true, removeOnFail: true, }; let job; let hooks; try { job = await this.jobQueue.add(jobData, jobOptions); console.log(`Started with job ID: ${job.id.toString()} (Execution ID: ${executionId})`); hooks = _1.WorkflowExecuteAdditionalData.getWorkflowHooksWorkerMain(data.executionMode, executionId, data.workflowData, { retryOf: data.retryOf ? data.retryOf.toString() : undefined }); hooks.executeHookFunctions('workflowExecuteBefore', []); } catch (error) { const hooks = _1.WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(data.executionMode, executionId, data.workflowData, { retryOf: data.retryOf ? data.retryOf.toString() : undefined }); await this.processError(error, new Date(), data.executionMode, executionId, hooks); throw error; } const workflowExecution = new PCancelable(async (resolve, reject, onCancel) => { onCancel.shouldReject = false; onCancel(async () => { await Queue.getInstance().stopJob(job); const hooksWorker = _1.WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(data.executionMode, executionId, data.workflowData, { retryOf: data.retryOf ? data.retryOf.toString() : undefined }); const error = new n8n_workflow_1.WorkflowOperationError('Workflow-Execution has been canceled!'); await this.processError(error, new Date(), data.executionMode, executionId, hooksWorker); reject(error); }); const jobData = job.finished(); const queueRecoveryInterval = config.get('queue.bull.queueRecoveryInterval'); const racingPromises = [jobData]; let clearWatchdogInterval; if (queueRecoveryInterval > 0) { let watchDogInterval; const watchDog = new Promise((res) => { watchDogInterval = setInterval(async () => { const currentJob = await this.jobQueue.getJob(job.id); if (currentJob === null) { res({ success: true }); } }, queueRecoveryInterval * 1000); }); racingPromises.push(watchDog); clearWatchdogInterval = () => { if (watchDogInterval) { clearInterval(watchDogInterval); watchDogInterval = undefined; } }; } try { await Promise.race(racingPromises); if (clearWatchdogInterval !== undefined) { clearWatchdogInterval(); } } catch (error) { const hooks = _1.WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(data.executionMode, executionId, data.workflowData, { retryOf: data.retryOf ? data.retryOf.toString() : undefined }); n8n_workflow_1.LoggerProxy.error(`Problem with execution ${executionId}: ${error.message}. Aborting.`); if (clearWatchdogInterval !== undefined) { clearWatchdogInterval(); } await this.processError(error, new Date(), data.executionMode, executionId, hooks); reject(error); } const executionDb = (await _1.Db.collections.Execution.findOne(executionId)); const fullExecutionData = _1.ResponseHelper.unflattenExecutionData(executionDb); const runData = { data: fullExecutionData.data, finished: fullExecutionData.finished, mode: fullExecutionData.mode, startedAt: fullExecutionData.startedAt, stoppedAt: fullExecutionData.stoppedAt, }; this.activeExecutions.remove(executionId, runData); hooks.executeHookFunctions('workflowExecuteAfter', [runData]); try { let saveDataErrorExecution = config.get('executions.saveDataOnError'); let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess'); if (data.workflowData.settings !== undefined) { saveDataErrorExecution = data.workflowData.settings.saveDataErrorExecution || saveDataErrorExecution; saveDataSuccessExecution = data.workflowData.settings.saveDataSuccessExecution || saveDataSuccessExecution; } const workflowDidSucceed = !runData.data.resultData.error; if ((workflowDidSucceed && saveDataSuccessExecution === 'none') || (!workflowDidSucceed && saveDataErrorExecution === 'none')) { await _1.Db.collections.Execution.delete(executionId); } } catch (err) { console.log('Error removing saved execution from database. More details: ', err); } resolve(runData); }); this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution); return executionId; } async runSubprocess(data, loadStaticData, restartExecutionId, responsePromise) { let startedAt = new Date(); const subprocess = child_process_1.fork(path_1.join(__dirname, 'WorkflowRunnerProcess.js')); if (loadStaticData === true && data.workflowData.id) { data.workflowData.staticData = await _1.WorkflowHelpers.getStaticDataById(data.workflowData.id); } const executionId = await this.activeExecutions.add(data, subprocess, restartExecutionId); let loadAllNodeTypes = false; for (const node of data.workflowData.nodes) { if (node.type === 'n8n-nodes-base.executeWorkflow') { loadAllNodeTypes = true; break; } } let nodeTypeData; let credentialTypeData; let credentialsOverwrites = this.credentialsOverwrites; if (loadAllNodeTypes) { nodeTypeData = _1.WorkflowHelpers.getAllNodeTypeData(); const credentialTypes = _1.CredentialTypes(); credentialTypeData = credentialTypes.credentialTypes; } else { nodeTypeData = _1.WorkflowHelpers.getNodeTypeData(data.workflowData.nodes); credentialTypeData = _1.WorkflowHelpers.getCredentialsDataByNodes(data.workflowData.nodes); credentialsOverwrites = {}; for (const credentialName of Object.keys(credentialTypeData)) { if (this.credentialsOverwrites[credentialName] !== undefined) { credentialsOverwrites[credentialName] = this.credentialsOverwrites[credentialName]; } } } data.executionId = executionId; data.nodeTypeData = nodeTypeData; data.credentialsOverwrite = this.credentialsOverwrites; data.credentialsTypeData = credentialTypeData; const workflowHooks = _1.WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId); try { subprocess.send({ type: 'startWorkflow', data }); } catch (error) { await this.processError(error, new Date(), data.executionMode, executionId, workflowHooks); return executionId; } let executionTimeout; let workflowTimeout = config.get('executions.timeout'); if (data.workflowData.settings && data.workflowData.settings.executionTimeout) { workflowTimeout = data.workflowData.settings.executionTimeout; } const processTimeoutFunction = (timeout) => { this.activeExecutions.stopExecution(executionId, 'timeout'); executionTimeout = setTimeout(() => subprocess.kill(), Math.max(timeout * 0.2, 5000)); }; if (workflowTimeout > 0) { workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout')) * 1000; executionTimeout = setTimeout(processTimeoutFunction, Math.max(5000, workflowTimeout), workflowTimeout); } const childExecutionIds = []; subprocess.on('message', async (message) => { n8n_workflow_1.LoggerProxy.debug(`Received child process message of type ${message.type} for execution ID ${executionId}.`, { executionId }); if (message.type === 'start') { startedAt = new Date(); if (workflowTimeout > 0) { clearTimeout(executionTimeout); executionTimeout = setTimeout(processTimeoutFunction, workflowTimeout, workflowTimeout); } } else if (message.type === 'end') { clearTimeout(executionTimeout); this.activeExecutions.remove(executionId, message.data.runData); } else if (message.type === 'sendResponse') { if (responsePromise) { responsePromise.resolve(_1.WebhookHelpers.decodeWebhookResponse(message.data.response)); } } else if (message.type === 'sendMessageToUI') { _1.WorkflowExecuteAdditionalData.sendMessageToUI.bind({ sessionId: data.sessionId })(message.data.source, message.data.message); } else if (message.type === 'processError') { clearTimeout(executionTimeout); const executionError = message.data.executionError; await this.processError(executionError, startedAt, data.executionMode, executionId, workflowHooks); } else if (message.type === 'processHook') { this.processHookMessage(workflowHooks, message.data); } else if (message.type === 'timeout') { const timeoutError = new n8n_workflow_1.WorkflowOperationError('Workflow execution timed out!'); this.processError(timeoutError, startedAt, data.executionMode, executionId); } else if (message.type === 'startExecution') { const executionId = await this.activeExecutions.add(message.data.runData); childExecutionIds.push(executionId); subprocess.send({ type: 'executionId', data: { executionId } }); } else if (message.type === 'finishExecution') { const executionIdIndex = childExecutionIds.indexOf(message.data.executionId); if (executionIdIndex !== -1) { childExecutionIds.splice(executionIdIndex, 1); } await this.activeExecutions.remove(message.data.executionId, message.data.result); } }); subprocess.on('exit', async (code, signal) => { if (signal === 'SIGTERM') { n8n_workflow_1.LoggerProxy.debug(`Subprocess for execution ID ${executionId} timed out.`, { executionId }); const timeoutError = new n8n_workflow_1.WorkflowOperationError('Workflow execution timed out!'); await this.processError(timeoutError, startedAt, data.executionMode, executionId, workflowHooks); } else if (code !== 0) { n8n_workflow_1.LoggerProxy.debug(`Subprocess for execution ID ${executionId} finished with error code ${code}.`, { executionId }); const executionError = new n8n_workflow_1.WorkflowOperationError('Workflow execution process did crash for an unknown reason!'); await this.processError(executionError, startedAt, data.executionMode, executionId, workflowHooks); } for (const executionId of childExecutionIds) { await this.activeExecutions.remove(executionId); } clearTimeout(executionTimeout); }); return executionId; } } exports.WorkflowRunner = WorkflowRunner; //# sourceMappingURL=WorkflowRunner.js.map