import { Db, IExecutionResponse, IResponseCallbackData, IWorkflowDb, NodeTypes, ResponseHelper, WebhookHelpers, WorkflowCredentials, WorkflowExecuteAdditionalData, } from '.'; import { INode, IRunExecutionData, NodeHelpers, WebhookHttpMethod, Workflow, } from 'n8n-workflow'; import * as express from 'express'; import { LoggerProxy as Logger, } from 'n8n-workflow'; export class WaitingWebhooks { async executeWebhook(httpMethod: WebhookHttpMethod, fullPath: string, req: express.Request, res: express.Response): Promise { Logger.debug(`Received waiting-webhoook "${httpMethod}" for path "${fullPath}"`); // Reset request parameters req.params = {}; // Remove trailing slash if (fullPath.endsWith('/')) { fullPath = fullPath.slice(0, -1); } const pathParts = fullPath.split('/'); const executionId = pathParts.shift(); const path = pathParts.join('/'); const execution = await Db.collections.Execution?.findOne(executionId); if (execution === undefined) { throw new ResponseHelper.ResponseError(`The execution "${executionId} does not exist.`, 404, 404); } const fullExecutionData = ResponseHelper.unflattenExecutionData(execution); if (fullExecutionData.finished === true || fullExecutionData.data.resultData.error) { throw new ResponseHelper.ResponseError(`The execution "${executionId} has finished already.`, 409, 409); } return this.startExecution(httpMethod, path, fullExecutionData, req, res); } async startExecution(httpMethod: WebhookHttpMethod, path: string, fullExecutionData: IExecutionResponse, req: express.Request, res: express.Response): Promise { const executionId = fullExecutionData.id; if (fullExecutionData.finished === true) { throw new Error('The execution did succeed and can so not be started again.'); } const lastNodeExecuted = fullExecutionData!.data.resultData.lastNodeExecuted as string; // Set the node as disabled so that the data does not get executed again as it would result // in starting the wait all over again fullExecutionData!.data.executionData!.nodeExecutionStack[0].node.disabled = true; // Remove waitTill information else the execution would stop fullExecutionData!.data.waitTill = undefined; // Remove the data of the node execution again else it will display the node as executed twice fullExecutionData!.data.resultData.runData[lastNodeExecuted].pop(); const workflowData = fullExecutionData.workflowData; const nodeTypes = NodeTypes(); const workflow = new Workflow({ id: workflowData.id!.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings }); const additionalData = await WorkflowExecuteAdditionalData.getBase(); const webhookData = NodeHelpers.getNodeWebhooks(workflow, workflow.getNode(lastNodeExecuted) as INode, additionalData).filter((webhook) => { return (webhook.httpMethod === httpMethod && webhook.path === path && webhook.webhookDescription.restartWebhook === true); })[0]; if (webhookData === undefined) { // If no data got found it means that the execution can not be started via a webhook. // Return 404 because we do not want to give any data if the execution exists or not. const errorMessage = `The execution "${executionId}" with webhook suffix path "${path}" is not known.`; throw new ResponseHelper.ResponseError(errorMessage, 404, 404); } const workflowStartNode = workflow.getNode(lastNodeExecuted); if (workflowStartNode === null) { throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404); } const runExecutionData = fullExecutionData.data as IRunExecutionData; return new Promise((resolve, reject) => { const executionMode = 'webhook'; WebhookHelpers.executeWebhook(workflow, webhookData, workflowData as IWorkflowDb, workflowStartNode, executionMode, undefined, runExecutionData, fullExecutionData.id, req, res, (error: Error | null, data: object) => { if (error !== null) { return reject(error); } resolve(data); }); }); } }